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-tabs": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@supabase-cache-helpers/postgrest-react-query": "^1.10.1",
"@supabase/ssr": "^0.4.1",
"@supabase/supabase-js": "^2.45.2",
"@tanstack/react-query": "^5.59.0",
"@tanstack/react-query-devtools": "^5.59.0",
"b2d-ventures": "file:",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",

View File

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

View File

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