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,15 +1,12 @@
"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;
@ -33,32 +30,15 @@ interface Business {
Projects: Project[]; Projects: Project[];
} }
export default function Find() { function getBusinesses(client: SupabaseClient, query: string | null) {
const searchParams = useSearchParams(); return client.from("Business").select("id, businessName, joinedDate").ilike("businessName", `%${query}%`);
const query = searchParams.get('query'); }
const [results, setResults] = useState<Business[]>([]);
const [loading, setLoading] = useState(true);
useEffect(() => { function getProjects(client: SupabaseClient, businessIds: string[]) {
const fetchData = async () => { return client
setLoading(true); .from("Project")
.select(
// fetch businesses `
const { data: businesses, error: businessError } = await supabase
.from('Business')
.select('id, businessName, joinedDate')
.ilike('businessName', `%${query}%`);
if (businessError) {
console.error('Error fetching businesses:', businessError);
setLoading(false);
return;
}
// fetch projects for these businesses
const { data: projects, error: projectError } = await supabase
.from('Project')
.select(`
id, id,
projectName, projectName,
businessId, businessId,
@ -67,65 +47,79 @@ export default function Find() {
totalInvestment, totalInvestment,
targetInvestment targetInvestment
) )
`) `
.in('businessId', businesses?.map(b => b.id) || []); )
.in("businessId", businessIds);
}
if (projectError) { function getTags(client: SupabaseClient, projectIds: string[]) {
console.error('Error fetching projects:', projectError); return client.from("ItemTag").select("itemId, Tag (value)").in("itemId", projectIds);
setLoading(false); }
return;
}
// fetch tags for these projects function getInvestmentCounts(client: SupabaseClient, projectIds: string[]) {
const { data: tags, error: tagError } = await supabase return client.from("InvestmentDeal").select("*", { count: "exact", head: true }).in("projectId", projectIds);
.from('ItemTag') }
.select('itemId, Tag (value)')
.in('itemId', projects?.map(p => p.id) || []);
if (tagError) { export default function Find() {
console.error('Error fetching tags:', tagError); const searchParams = useSearchParams();
} const query = searchParams.get("query");
// const query = "neon";
// fetch investment counts let supabase = createSupabaseClient();
const { data: investmentCounts, error: investmentError } = await supabase
.from('InvestmentDeal')
.select('projectId, count', { count: 'exact', head: false })
.in('projectId', projects?.map(p => p.id) || []);
if (investmentError) { const {
console.error('Error fetching investment counts:', investmentError); data: businesses,
} isLoading: isLoadingBusinesses,
error: businessError,
} = useQuery(getBusinesses(supabase, query));
// combine all data const businessIds = businesses?.map((b) => b.id) || [];
const processedResults = businesses?.map(business => ({ const {
data: projects,
isLoading: isLoadingProjects,
error: projectError,
} = useQuery(getProjects(supabase, businessIds), { enabled: businessIds.length > 0 });
const projectIds = projects?.map((p) => p.id) || [];
const {
data: tags,
isLoading: isLoadingTags,
error: tagError,
} = useQuery(getTags(supabase, projectIds), { enabled: projectIds.length > 0 });
const {
data: investmentCounts,
isLoading: isLoadingInvestments,
error: investmentError,
} = useQuery(getInvestmentCounts(supabase, projectIds), { enabled: projectIds.length > 0 });
// -----
const isLoading = isLoadingBusinesses || isLoadingProjects || isLoadingTags || isLoadingInvestments;
const error = businessError || projectError || tagError || investmentError;
const results: Business[] =
businesses?.map((business) => ({
...business, ...business,
Projects: projects Projects:
?.filter(project => project.businessId === business.id) projects
.map(project => ({ ?.filter((project) => project.businessId === business.id)
.map((project) => ({
...project, ...project,
tags: tags tags: tags?.filter((tag) => tag.itemId === project.id).map((tag) => tag.Tag.value) || [],
?.filter(t => t.itemId === project.id) investmentCount: investmentCounts?.find((ic) => ic.projectId === project.id)?.count || 0,
.map(t => t.Tag.values as unknown as string) || [], })) || [],
investmentCount: investmentCounts?.find(ic => ic.projectId === project.id)?.count || 0
})) || []
})) || []; })) || [];
setResults(processedResults); if (isLoading) return <p>Loading...</p>;
setLoading(false); if (error) return <p>Error fetching data: {error.message}</p>;
};
if (query) {
fetchData();
}
}, [query]);
return ( return (
<div> <div>
{loading && <p>Loading...</p>} {results.length === 0 && <p>No results found.</p>}
{!loading && results.length === 0 && <p>No results found.</p>} {results.length > 0 && (
{!loading && results.length > 0 && (
<ul> <ul>
{results.map(business => ( {results.map((business) => (
<li key={business.id}> <li key={business.id}>
<h2>{business.businessName}</h2> <h2>{business.businessName}</h2>
<p>Joined Date: {new Date(business.joinedDate).toLocaleDateString()}</p> <p>Joined Date: {new Date(business.joinedDate).toLocaleDateString()}</p>
@ -137,7 +131,7 @@ export default function Find() {
<p>Min Investment: ${project.ProjectInvestmentDetail[0]?.minInvestment}</p> <p>Min Investment: ${project.ProjectInvestmentDetail[0]?.minInvestment}</p>
<p>Total Investment: ${project.ProjectInvestmentDetail[0]?.totalInvestment}</p> <p>Total Investment: ${project.ProjectInvestmentDetail[0]?.totalInvestment}</p>
<p>Target Investment: ${project.ProjectInvestmentDetail[0]?.targetInvestment}</p> <p>Target Investment: ${project.ProjectInvestmentDetail[0]?.targetInvestment}</p>
<p>Tags: {project.tags.join(', ')}</p> <p>Tags: {project.tags.join(", ")}</p>
</li> </li>
))} ))}
</ul> </ul>
@ -145,6 +139,7 @@ export default function Find() {
))} ))}
</ul> </ul>
)} )}
<ReactQueryDevtools initialIsOpen={false} />
</div> </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,6 +25,7 @@ interface RootLayoutProps {
export default function RootLayout({ children }: RootLayoutProps) { export default function RootLayout({ children }: RootLayoutProps) {
return ( return (
<ReactQueryClientProvider>
<html lang="en"> <html lang="en">
<head /> <head />
<body className={`${montserrat.className}`}> <body className={`${montserrat.className}`}>
@ -35,5 +37,6 @@ export default function RootLayout({ children }: RootLayoutProps) {
</ThemeProvider> </ThemeProvider>
</body> </body>
</html> </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>;
};