Merge branch 'back-end' of https://github.com/Sosokker/B2D-Ventures into back-end

This commit is contained in:
Sosokker 2024-11-02 19:00:34 +07:00
commit e29eeab868
7 changed files with 121 additions and 64 deletions

View File

@ -13,24 +13,34 @@ import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
import FollowShareButtons from "./followShareButton"; import FollowShareButtons from "./followShareButton";
import { getProjectData } from "@/lib/data/projectQuery"; import { getProjectData } from "@/lib/data/projectQuery";
import { getDealList } from "@/app/api/dealApi";
import { sumByKey, toPercentage } from "@/lib/utils";
export default async function ProjectDealPage({ params }: { params: { id: number } }) { export default async function ProjectDealPage({ params }: { params: { id: number } }) {
const supabase = createSupabaseClient(); const supabase = createSupabaseClient();
const { data: projectData, error: projectDataError } = await getProjectData(supabase, params.id); const { data: projectData, error: projectDataError } = await getProjectData(supabase, params.id);
const carouselData = [ if (!projectData) {
{ src: "/boiler1.jpg", alt: "Boiler 1" }, return <div>No data available</div>;
{ src: "/boiler1.jpg", alt: "Boiler 1" }, }
{ src: "/boiler1.jpg", alt: "Boiler 1" },
{ src: "/boiler1.jpg", alt: "Boiler 1" },
{ src: "/boiler1.jpg", alt: "Boiler 1" },
];
if (projectDataError) { if (projectDataError) {
return <div>Error</div>; return <div>Error</div>;
} }
const projectBusinessOwnerId = projectData.user_id;
const dealList = await getDealList(projectBusinessOwnerId);
const totalDealAmount = sumByKey(dealList, "deal_amount");
// timeDiff, if negative convert to zero
const timeDiff = Math.max((new Date(projectData.investment_deadline)).getTime() - new Date().getTime(), 0)
const hourLeft = Math.floor(timeDiff / (1000 * 60 * 60));
const carouselData = Array(5).fill({
src: projectData.card_image_url || "/boiler1.jpg",
alt: `${projectData.project_name} Image`,
});
return ( return (
<div className="container max-w-screen-xl my-5"> <div className="container max-w-screen-xl my-5">
<div className="flex flex-col gap-y-10"> <div className="flex flex-col gap-y-10">
@ -82,24 +92,33 @@ export default async function ProjectDealPage({ params }: { params: { id: number
<div id="stats" className="flex flex-col w-full mt-4 pl-12"> <div id="stats" className="flex flex-col w-full mt-4 pl-12">
<div className="pl-5"> <div className="pl-5">
<span> <span>
<h1 className="font-semibold text-xl md:text-4xl mt-8">${projectData?.total_investment}</h1> <h1 className="font-semibold text-xl md:text-4xl mt-8">${totalDealAmount}</h1>
<p className="text-sm md:text-lg"> 5% raised of \$5M max goal</p> <p className="text-sm md:text-lg">
{toPercentage(totalDealAmount, projectData?.target_investment)}%
raised of ${projectData?.target_investment} max goal
</p>
<Progress <Progress
value={projectData?.total_investment / projectData?.target_investment} value={toPercentage(totalDealAmount, projectData?.target_investment)}
className="w-[60%] h-3 mt-3" className="w-[60%] h-3 mt-3"
/> />
</span> </span>
<span> <span>
<h1 className="font-semibold text-4xl md:mt-8"> <h1 className="font-semibold text-4xl md:mt-8">
<p className="text-xl md:text-4xl">{projectData?.total_investment}</p> <p className="text-xl md:text-4xl">{dealList.length}</p>
</h1> </h1>
<p className="text-sm md:text-lg"> Investors</p> <p className="text-sm md:text-lg">Investors</p>
</span> </span>
<Separator decorative className="mt-3 w-3/4 ml-5" /> <Separator decorative className="mt-3 w-3/4 ml-5" />
<span> <span>
<h1 className="font-semibold text-xl md:text-4xl mt-8 ml-5"></h1> <h1 className="font-semibold text-xl md:text-4xl mt-8 ml-5"></h1>
<p className="text-xl md:text-4xl">1 hours</p> {projectData?.investment_deadline ? (
<p> Left to invest</p> <>
<p className="text-xl md:text-4xl">{Math.floor(hourLeft)} hours</p>
<p>Left to invest</p>
</>
) : (
<p className="text-xl md:text-4xl">No deadline</p>
)}
</span> </span>
<Button className="mt-5 w-3/4 h-12"> <Button className="mt-5 w-3/4 h-12">
<Link href={`/invest/${params.id}`}>Invest in {projectData?.project_name}</Link> <Link href={`/invest/${params.id}`}>Invest in {projectData?.project_name}</Link>
@ -121,8 +140,8 @@ export default async function ProjectDealPage({ params }: { params: { id: number
<Tabs.Content value="pitch"> <Tabs.Content value="pitch">
<Card> <Card>
<CardHeader> <CardHeader>
<CardTitle></CardTitle> <CardTitle>{projectData.project_name}</CardTitle>
<CardDescription></CardDescription> <CardDescription />
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="prose prose-sm max-w-none "> <div className="prose prose-sm max-w-none ">

View File

@ -1,5 +1,4 @@
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient"; import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
import { getCurrentUserID } from "./userApi";
export type Deal = { export type Deal = {
deal_amount: number; deal_amount: number;
@ -7,9 +6,14 @@ export type Deal = {
investor_id: string; investor_id: string;
}; };
export async function getDealList() { export async function getDealList(userId: string | undefined) {
if (!userId) {
// console.error("No deal list of this user was found");
return []; // Exit on error
}
const supabase = createSupabaseClient(); const supabase = createSupabaseClient();
// get id of investor who invests in the business // get id of investors who invest in the business
const { data: dealData, error: dealError } = await supabase const { data: dealData, error: dealError } = await supabase
.from("business") .from("business")
.select( .select(
@ -21,18 +25,18 @@ export async function getDealList() {
) )
` `
) )
.eq("user_id", await getCurrentUserID()) .eq("user_id", userId)
.single(); .single();
if (dealError) { if (dealError) {
alert(JSON.stringify(dealError)); // alert(JSON.stringify(dealError));
console.error("Error fetching deal list:", dealError); console.error("Error fetching deal list:", dealError);
return; // Exit on error return []; // Exit on error
} }
if (!dealData || !dealData.project.length) { if (!dealData || !dealData.project.length) {
alert("No project available"); alert("No project available");
return; // Exit if there's no data return []; // Exit if there's no data
} }
const investorIdList = dealData.project[0].investment_deal.map((deal) => deal.investor_id); const investorIdList = dealData.project[0].investment_deal.map((deal) => deal.investor_id);
@ -53,16 +57,22 @@ export async function getDealList() {
if (sortedDealDataError) { if (sortedDealDataError) {
alert(JSON.stringify(sortedDealDataError)); alert(JSON.stringify(sortedDealDataError));
console.error("Error sorting deal list:", sortedDealDataError); console.error("Error sorting deal list:", sortedDealDataError);
return; // Exit on error return []; // Exit on error
} }
// console.log(sortedDealData)
return sortedDealData; return sortedDealData;
} }
// #TODO fix query to be non unique // #TODO fix query to be non unique
export async function getRecentDealData() { export async function getRecentDealData(userId: string | undefined) {
if (!userId) {
console.error("User not found");
return; // Exit on error
}
const supabase = createSupabaseClient(); const supabase = createSupabaseClient();
const dealList = await getDealList(); const dealList = await getDealList(userId);
if (!dealList) { if (!dealList) {
// #TODO div error // #TODO div error
@ -100,6 +110,8 @@ export async function getRecentDealData() {
const recentDealData = recentDealList.map((item, index) => { const recentDealData = recentDealList.map((item, index) => {
return { ...item, ...recentUserData[index] }; return { ...item, ...recentUserData[index] };
}); });
return recentDealData; return recentDealData;
} }
@ -121,8 +133,6 @@ export function convertToGraphData(deals: Deal[]): Record<string, number> {
// Create a sorted graph data object // Create a sorted graph data object
const sortedGraphData: Record<string, number> = {}; const sortedGraphData: Record<string, number> = {};
sortedKeys.forEach((key) => { sortedKeys.forEach((key) => {sortedGraphData[key] = graphData[key]});
sortedGraphData[key] = graphData[key];
});
return sortedGraphData; return sortedGraphData;
} }

View File

@ -1,13 +1,15 @@
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { Deal, getDealList, convertToGraphData, getRecentDealData } from "../api/dealApi"; import { Deal, getDealList, convertToGraphData, getRecentDealData } from "../api/dealApi";
import { RecentDealData } from "@/components/recent-funds"; import { RecentDealData } from "@/components/recent-funds";
import { getCurrentUserID } from "../api/userApi";
// custom hook for deal list // custom hook for deal list
export function useDealList() { export function useDealList() {
const [dealList, setDealList] = useState<Deal[]>(); const [dealList, setDealList] = useState<Deal[]>([]);
const fetchDealList = async () => { const fetchDealList = async () => {
setDealList(await getDealList()); // set the state to the deal list of current business user
setDealList(await getDealList(await getCurrentUserID()));
} }
useEffect(() => { useEffect(() => {
@ -21,7 +23,8 @@ export function useGraphData() {
const [graphData, setGraphData] = useState({}); const [graphData, setGraphData] = useState({});
const fetchGraphData = async () => { const fetchGraphData = async () => {
const dealList = await getDealList(); // fetch the state to the deal list of current business user
const dealList = await getDealList(await getCurrentUserID());
if (dealList) { if (dealList) {
setGraphData(convertToGraphData(dealList)); setGraphData(convertToGraphData(dealList));
} }
@ -38,7 +41,8 @@ export function useRecentDealData() {
const [recentDealData, setRecentDealData] = useState<RecentDealData[]>(); const [recentDealData, setRecentDealData] = useState<RecentDealData[]>();
const fetchRecentDealData = async () => { const fetchRecentDealData = async () => {
setRecentDealData(await getRecentDealData()); // set the state to the deal list of current business user
setRecentDealData(await getRecentDealData(await getCurrentUserID()));
} }
useEffect(() => { useEffect(() => {

View File

@ -13,6 +13,7 @@ import { RecentFunds } from "@/components/recent-funds";
import { useState } from "react"; import { useState } from "react";
import { useDealList, useGraphData, useRecentDealData } from "./hook"; import { useDealList, useGraphData, useRecentDealData } from "./hook";
import { sumByKey } from "@/lib/utils";
export default function Dashboard() { export default function Dashboard() {
const [graphType, setGraphType] = useState("line"); const [graphType, setGraphType] = useState("line");
@ -20,17 +21,9 @@ export default function Dashboard() {
const dealList = useDealList(); const dealList = useDealList();
// #TODO dependency injection refactor + define default value inside function (and not here) // #TODO dependency injection refactor + define default value inside function (and not here)
const recentDealData = useRecentDealData() || []; const recentDealData = useRecentDealData() || [];
const totalDealAmount = dealList?.reduce((sum, deal) => sum + deal.deal_amount, 0) || 0;
return ( return (
<> <>
{/* {dealList?.map((deal, index) => (
<div key={index} className="deal-item">
<p>Deal Amount: {deal.deal_amount}</p>
<p>Created Time: {new Date(deal.created_time).toUTCString()}</p>
<p>Investor ID: {deal.investor_id}</p>
</div>
))} */}
<div className="md:hidden"> <div className="md:hidden">
<Image <Image
src="/examples/dashboard-light.png" src="/examples/dashboard-light.png"
@ -78,7 +71,7 @@ export default function Dashboard() {
</svg> </svg>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
<div className="text-2xl font-bold">${totalDealAmount}</div> <div className="text-2xl font-bold">${sumByKey(dealList, "deal_amount")}</div>
{/* <p className="text-xs text-muted-foreground"> {/* <p className="text-xs text-muted-foreground">
+20.1% from last month +20.1% from last month
</p> */} </p> */}

View File

@ -18,7 +18,7 @@ const BUCKET_PITCH_APPLICATION_NAME = "project-application";
export default function ApplyProject() { export default function ApplyProject() {
const [isSuccess, setIsSuccess] = useState(true); const [isSuccess, setIsSuccess] = useState(true);
const onSubmit: SubmitHandler<projectSchema> = async (data) => { const onSubmit: SubmitHandler<projectSchema> = async (data) => {
alert("มาแน้ววว"); // alert("มาแน้ววว");
await sendApplication(data); await sendApplication(data);
// console.table(data); // console.table(data);
// console.log(typeof data["projectPhotos"], data["projectPhotos"]); // console.log(typeof data["projectPhotos"], data["projectPhotos"]);

View File

@ -6,28 +6,28 @@ async function getTopProjects(client: SupabaseClient, numberOfRecords: number =
.from("project") .from("project")
.select( .select(
` `
id, id,
project_name, project_name,
business_id, business_id,
published_time, published_time,
project_short_description, project_short_description,
card_image_url, card_image_url,
project_investment_detail ( project_investment_detail (
min_investment, min_investment,
total_investment, total_investment,
target_investment, target_investment,
investment_deadline investment_deadline
), ),
project_tag ( project_tag (
tag ( tag (
id, id,
value value
)
),
business (
location
) )
` ),
business (
location
)
`
) )
.order("published_time", { ascending: false }) .order("published_time", { ascending: false })
.limit(numberOfRecords); .limit(numberOfRecords);
@ -79,6 +79,7 @@ async function getProjectData(client: SupabaseClient, projectId: number) {
project_short_description, project_short_description,
project_description, project_description,
published_time, published_time,
card_image_url,
...project_investment_detail!inner ( ...project_investment_detail!inner (
min_investment, min_investment,
total_investment, total_investment,
@ -89,6 +90,9 @@ async function getProjectData(client: SupabaseClient, projectId: number) {
...tag!inner ( ...tag!inner (
tag_name:value tag_name:value
) )
),
...business (
user_id
) )
` `
) )

View File

@ -4,3 +4,30 @@ import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs))
} }
export function sum(list: any[]) {
if (!list || list.length === 0) {
return 0;
}
return list.reduce((total, num) => total + num, 0);
}
export function sumByKey(list: any[], key: string) {
// example usage
// const items = [
// { amount: 10 },
// { amount: 20 },
// { amount: 30 },
// { amount: 40 }
// ];
// const totalAmount = sumByKey(items, 'amount');
// console.log(totalAmount); // Output: 100
return list.reduce((total, obj) => total + (obj[key] || 0), 0);
}
export function toPercentage(part: number, total: number): number {
if (total === 0) return 0; // Prevent division by zero
return Number(((part * 100) / total).toFixed(2));
}