Use React-Query to query business data

This commit is contained in:
sirin 2024-10-02 21:54:14 +07:00
parent 30e4c9fe10
commit ebd38f5da1
5 changed files with 635 additions and 429 deletions

767
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -19,8 +19,11 @@
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
"@radix-ui/react-tabs": "^1.1.0", "@radix-ui/react-tabs": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2", "@radix-ui/react-tooltip": "^1.1.2",
"@supabase-cache-helpers/postgrest-react-query": "^1.10.1",
"@supabase/ssr": "^0.4.1", "@supabase/ssr": "^0.4.1",
"@supabase/supabase-js": "^2.45.2", "@supabase/supabase-js": "^2.45.2",
"@tanstack/react-query": "^5.59.0",
"@tanstack/react-query-devtools": "^5.59.0",
"b2d-ventures": "file:", "b2d-ventures": "file:",
"class-variance-authority": "^0.7.0", "class-variance-authority": "^0.7.0",
"clsx": "^2.1.1", "clsx": "^2.1.1",

View File

@ -1,150 +1,145 @@
"use client"; "use client";
import { useSearchParams } from 'next/navigation'; import { useSearchParams } from "next/navigation";
import { createClient } from '@supabase/supabase-js'; import { createClient, SupabaseClient } from "@supabase/supabase-js";
import { useEffect, useState } from 'react'; import { useEffect, useState } from "react";
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL || ''; import { dehydrate, HydrationBoundary, QueryClient } from "@tanstack/react-query";
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || ''; import { useQuery } from "@supabase-cache-helpers/postgrest-react-query";
if (!supabaseUrl || !supabaseKey) { import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
throw new Error('Supabase URL and Key must be provided');
}
const supabase = createClient(supabaseUrl, supabaseKey);
interface ProjectInvestmentDetail { interface ProjectInvestmentDetail {
minInvestment: number; minInvestment: number;
totalInvestment: number; totalInvestment: number;
targetInvestment: number; targetInvestment: number;
} }
interface Project { interface Project {
id: string; id: string;
projectName: string; projectName: string;
businessId: string; businessId: string;
investmentCount: number; investmentCount: number;
ProjectInvestmentDetail: ProjectInvestmentDetail[]; ProjectInvestmentDetail: ProjectInvestmentDetail[];
tags: string[]; tags: string[];
} }
interface Business { interface Business {
id: string; id: string;
businessName: string; businessName: string;
joinedDate: string; joinedDate: string;
Projects: Project[]; Projects: Project[];
}
function getBusinesses(client: SupabaseClient, query: string | null) {
return client.from("Business").select("id, businessName, joinedDate").ilike("businessName", `%${query}%`);
}
function getProjects(client: SupabaseClient, businessIds: string[]) {
return client
.from("Project")
.select(
`
id,
projectName,
businessId,
ProjectInvestmentDetail (
minInvestment,
totalInvestment,
targetInvestment
)
`
)
.in("businessId", businessIds);
}
function getTags(client: SupabaseClient, projectIds: string[]) {
return client.from("ItemTag").select("itemId, Tag (value)").in("itemId", projectIds);
}
function getInvestmentCounts(client: SupabaseClient, projectIds: string[]) {
return client.from("InvestmentDeal").select("*", { count: "exact", head: true }).in("projectId", projectIds);
} }
export default function Find() { export default function Find() {
const searchParams = useSearchParams(); const searchParams = useSearchParams();
const query = searchParams.get('query'); const query = searchParams.get("query");
const [results, setResults] = useState<Business[]>([]); // const query = "neon";
const [loading, setLoading] = useState(true);
useEffect(() => { let supabase = createSupabaseClient();
const fetchData = async () => {
setLoading(true);
// fetch businesses const {
const { data: businesses, error: businessError } = await supabase data: businesses,
.from('Business') isLoading: isLoadingBusinesses,
.select('id, businessName, joinedDate') error: businessError,
.ilike('businessName', `%${query}%`); } = useQuery(getBusinesses(supabase, query));
if (businessError) { const businessIds = businesses?.map((b) => b.id) || [];
console.error('Error fetching businesses:', businessError); const {
setLoading(false); data: projects,
return; isLoading: isLoadingProjects,
} error: projectError,
} = useQuery(getProjects(supabase, businessIds), { enabled: businessIds.length > 0 });
// fetch projects for these businesses const projectIds = projects?.map((p) => p.id) || [];
const { data: projects, error: projectError } = await supabase const {
.from('Project') data: tags,
.select(` isLoading: isLoadingTags,
id, error: tagError,
projectName, } = useQuery(getTags(supabase, projectIds), { enabled: projectIds.length > 0 });
businessId,
ProjectInvestmentDetail (
minInvestment,
totalInvestment,
targetInvestment
)
`)
.in('businessId', businesses?.map(b => b.id) || []);
if (projectError) { const {
console.error('Error fetching projects:', projectError); data: investmentCounts,
setLoading(false); isLoading: isLoadingInvestments,
return; error: investmentError,
} } = useQuery(getInvestmentCounts(supabase, projectIds), { enabled: projectIds.length > 0 });
// fetch tags for these projects // -----
const { data: tags, error: tagError } = await supabase
.from('ItemTag')
.select('itemId, Tag (value)')
.in('itemId', projects?.map(p => p.id) || []);
if (tagError) { const isLoading = isLoadingBusinesses || isLoadingProjects || isLoadingTags || isLoadingInvestments;
console.error('Error fetching tags:', tagError); const error = businessError || projectError || tagError || investmentError;
}
// fetch investment counts const results: Business[] =
const { data: investmentCounts, error: investmentError } = await supabase businesses?.map((business) => ({
.from('InvestmentDeal') ...business,
.select('projectId, count', { count: 'exact', head: false }) Projects:
.in('projectId', projects?.map(p => p.id) || []); projects
?.filter((project) => project.businessId === business.id)
.map((project) => ({
...project,
tags: tags?.filter((tag) => tag.itemId === project.id).map((tag) => tag.Tag.value) || [],
investmentCount: investmentCounts?.find((ic) => ic.projectId === project.id)?.count || 0,
})) || [],
})) || [];
if (investmentError) { if (isLoading) return <p>Loading...</p>;
console.error('Error fetching investment counts:', investmentError); if (error) return <p>Error fetching data: {error.message}</p>;
}
// combine all data return (
const processedResults = businesses?.map(business => ({ <div>
...business, {results.length === 0 && <p>No results found.</p>}
Projects: projects {results.length > 0 && (
?.filter(project => project.businessId === business.id) <ul>
.map(project => ({ {results.map((business) => (
...project, <li key={business.id}>
tags: tags <h2>{business.businessName}</h2>
?.filter(t => t.itemId === project.id) <p>Joined Date: {new Date(business.joinedDate).toLocaleDateString()}</p>
.map(t => t.Tag.values as unknown as string) || [], <ul>
investmentCount: investmentCounts?.find(ic => ic.projectId === project.id)?.count || 0 {business.Projects.map((project) => (
})) || [] <li key={project.id}>
})) || []; <h3>{project.projectName}</h3>
<p>Investment Count: {project.investmentCount}</p>
setResults(processedResults); <p>Min Investment: ${project.ProjectInvestmentDetail[0]?.minInvestment}</p>
setLoading(false); <p>Total Investment: ${project.ProjectInvestmentDetail[0]?.totalInvestment}</p>
}; <p>Target Investment: ${project.ProjectInvestmentDetail[0]?.targetInvestment}</p>
<p>Tags: {project.tags.join(", ")}</p>
if (query) { </li>
fetchData(); ))}
} </ul>
}, [query]); </li>
))}
return ( </ul>
<div> )}
{loading && <p>Loading...</p>} <ReactQueryDevtools initialIsOpen={false} />
{!loading && results.length === 0 && <p>No results found.</p>} </div>
{!loading && results.length > 0 && ( );
<ul>
{results.map(business => (
<li key={business.id}>
<h2>{business.businessName}</h2>
<p>Joined Date: {new Date(business.joinedDate).toLocaleDateString()}</p>
<ul>
{business.Projects.map((project) => (
<li key={project.id}>
<h3>{project.projectName}</h3>
<p>Investment Count: {project.investmentCount}</p>
<p>Min Investment: ${project.ProjectInvestmentDetail[0]?.minInvestment}</p>
<p>Total Investment: ${project.ProjectInvestmentDetail[0]?.totalInvestment}</p>
<p>Target Investment: ${project.ProjectInvestmentDetail[0]?.targetInvestment}</p>
<p>Tags: {project.tags.join(', ')}</p>
</li>
))}
</ul>
</li>
))}
</ul>
)}
</div>
);
} }

View File

@ -1,6 +1,7 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { Montserrat } from "next/font/google"; import { Montserrat } from "next/font/google";
import { ThemeProvider } from "@/components/theme-provider"; import { ThemeProvider } from "@/components/theme-provider";
import { ReactQueryClientProvider } from "@/components/ReactQueryClientProvider";
import "@/app/globals.css"; import "@/app/globals.css";
import { NavigationBar } from "@/components/navigationBar/nav"; import { NavigationBar } from "@/components/navigationBar/nav";
@ -24,16 +25,18 @@ interface RootLayoutProps {
export default function RootLayout({ children }: RootLayoutProps) { export default function RootLayout({ children }: RootLayoutProps) {
return ( return (
<html lang="en"> <ReactQueryClientProvider>
<head /> <html lang="en">
<body className={`${montserrat.className}`}> <head />
<ThemeProvider attribute="class" defaultTheme="system" enableSystem> <body className={`${montserrat.className}`}>
<div className="relative flex min-h-screen flex-col"> <ThemeProvider attribute="class" defaultTheme="system" enableSystem>
<NavigationBar /> <div className="relative flex min-h-screen flex-col">
<div className="flex-1 bg-background">{children}</div> <NavigationBar />
</div> <div className="flex-1 bg-background">{children}</div>
</ThemeProvider> </div>
</body> </ThemeProvider>
</html> </body>
</html>
</ReactQueryClientProvider>
); );
} }

View File

@ -0,0 +1,20 @@
"use client";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useState } from "react";
export const ReactQueryClientProvider = ({ children }: { children: React.ReactNode }) => {
const [queryClient] = useState(
() =>
new QueryClient({
defaultOptions: {
queries: {
// With SSR, we usually want to set some default staleTime
// above 0 to avoid refetching immediately on the client
staleTime: 60 * 1000,
},
},
})
);
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
};