mirror of
https://github.com/Sosokker/B2D-Ventures.git
synced 2025-12-19 05:54:06 +01:00
Merge branch 'front-end' into back-end
This commit is contained in:
commit
d5c6590cd3
@ -5,13 +5,12 @@ import ReactMarkdown from "react-markdown";
|
|||||||
|
|
||||||
import * as Tabs from "@radix-ui/react-tabs";
|
import * as Tabs from "@radix-ui/react-tabs";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
|
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription, CardFooter } from "@/components/ui/card";
|
||||||
import { Progress } from "@/components/ui/progress";
|
import { Progress } from "@/components/ui/progress";
|
||||||
import { Separator } from "@/components/ui/separator";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
|
import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
|
||||||
import FollowShareButtons from "./followShareButton";
|
import FollowShareButtons from "./followShareButton";
|
||||||
import { DisplayFullImage } from "./displayImage";
|
|
||||||
import { getProjectData } from "@/lib/data/projectQuery";
|
import { getProjectData } from "@/lib/data/projectQuery";
|
||||||
import { getDealList } from "@/app/api/dealApi";
|
import { getDealList } from "@/app/api/dealApi";
|
||||||
import { sumByKey, toPercentage } from "@/lib/utils";
|
import { sumByKey, toPercentage } from "@/lib/utils";
|
||||||
@ -19,6 +18,7 @@ import { redirect } from "next/navigation";
|
|||||||
import { isOwnerOfProject } from "./query";
|
import { isOwnerOfProject } from "./query";
|
||||||
import { UpdateTab } from "./UpdateTab";
|
import { UpdateTab } from "./UpdateTab";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
|
import Gallery from "@/components/carousel";
|
||||||
|
|
||||||
const PHOTO_MATERIAL_ID = 2;
|
const PHOTO_MATERIAL_ID = 2;
|
||||||
|
|
||||||
@ -75,7 +75,6 @@ export default async function ProjectDealPage({ params }: { params: { id: number
|
|||||||
? projectMaterial.flatMap((item) =>
|
? projectMaterial.flatMap((item) =>
|
||||||
(item.material_url || ["/boiler1.jpg"]).map((url: string) => ({
|
(item.material_url || ["/boiler1.jpg"]).map((url: string) => ({
|
||||||
src: url,
|
src: url,
|
||||||
alt: "Image",
|
|
||||||
}))
|
}))
|
||||||
)
|
)
|
||||||
: [{ src: "/boiler1.jpg", alt: "Default Boiler Image" }];
|
: [{ src: "/boiler1.jpg", alt: "Default Boiler Image" }];
|
||||||
@ -104,35 +103,8 @@ export default async function ProjectDealPage({ params }: { params: { id: number
|
|||||||
</div>
|
</div>
|
||||||
<div id="sub-content" className="flex flex-row mt-5">
|
<div id="sub-content" className="flex flex-row mt-5">
|
||||||
{/* image carousel */}
|
{/* image carousel */}
|
||||||
<div id="image-carousel" className="shrink-0 w-[700px] flex flex-col">
|
<div id="image-carousel" className="w-full">
|
||||||
{/* first carousel */}
|
<Gallery images={carouselData} />
|
||||||
<Carousel className="w-full h-[400px] ml-1 overflow-hidden">
|
|
||||||
<CarouselContent className="flex h-full">
|
|
||||||
{carouselData.map((item, index) => (
|
|
||||||
<CarouselItem key={index}>
|
|
||||||
<Image src={item.src} alt={item.alt} width={700} height={400} className="rounded-lg object-cover" />
|
|
||||||
</CarouselItem>
|
|
||||||
))}
|
|
||||||
</CarouselContent>
|
|
||||||
<CarouselPrevious className="absolute left-2 top-1/2 transform -translate-y-1/2 z-10 text-white bg-black opacity-50 hover:opacity-100" />
|
|
||||||
<CarouselNext className="absolute right-2 top-1/2 transform -translate-y-1/2 z-10 text-white bg-black opacity-50 hover:opacity-100" />
|
|
||||||
</Carousel>
|
|
||||||
{/* second carousel */}
|
|
||||||
<Carousel className="w-full ml-1 h-[100px] mt-5 overflow-hidden">
|
|
||||||
<CarouselContent className="flex space-x-1 h-[100px]">
|
|
||||||
{carouselData.map((item, index) => (
|
|
||||||
<CarouselItem key={index} className="flex">
|
|
||||||
<DisplayFullImage
|
|
||||||
src={item.src}
|
|
||||||
alt={item.alt}
|
|
||||||
width={200}
|
|
||||||
height={100}
|
|
||||||
className="rounded-lg object-cover h-[100px] w-[200px]"
|
|
||||||
/>
|
|
||||||
</CarouselItem>
|
|
||||||
))}
|
|
||||||
</CarouselContent>
|
|
||||||
</Carousel>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Card className="w-[80%] ml-10 shadow-sm">
|
<Card className="w-[80%] ml-10 shadow-sm">
|
||||||
|
|||||||
@ -1,53 +0,0 @@
|
|||||||
import { useEffect, useState } from "react";
|
|
||||||
import { Deal, getDealList, convertToGraphData, getRecentDealData } from "../api/dealApi";
|
|
||||||
import { RecentDealData } from "@/components/recent-funds";
|
|
||||||
import { getCurrentUserID } from "../api/userApi";
|
|
||||||
|
|
||||||
// custom hook for deal list
|
|
||||||
export function useDealList() {
|
|
||||||
const [dealList, setDealList] = useState<Deal[]>([]);
|
|
||||||
|
|
||||||
const fetchDealList = async () => {
|
|
||||||
// set the state to the deal list of current business user
|
|
||||||
setDealList(await getDealList(await getCurrentUserID()));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchDealList();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return dealList;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useGraphData() {
|
|
||||||
const [graphData, setGraphData] = useState({});
|
|
||||||
|
|
||||||
const fetchGraphData = async () => {
|
|
||||||
// fetch the state to the deal list of current business user
|
|
||||||
const dealList = await getDealList(await getCurrentUserID());
|
|
||||||
if (dealList) {
|
|
||||||
setGraphData(convertToGraphData(dealList));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchGraphData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return graphData;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useRecentDealData() {
|
|
||||||
const [recentDealData, setRecentDealData] = useState<RecentDealData[]>();
|
|
||||||
|
|
||||||
const fetchRecentDealData = async () => {
|
|
||||||
// set the state to the deal list of current business user
|
|
||||||
setRecentDealData(await getRecentDealData(await getCurrentUserID()));
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
fetchRecentDealData();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return recentDealData;
|
|
||||||
}
|
|
||||||
@ -5,84 +5,104 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|||||||
import { Overview } from "@/components/ui/overview";
|
import { Overview } from "@/components/ui/overview";
|
||||||
import { RecentFunds } from "@/components/recent-funds";
|
import { RecentFunds } from "@/components/recent-funds";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect, useState } from "react";
|
||||||
import { useDealList } from "./hook";
|
|
||||||
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
|
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
|
||||||
import useSession from "@/lib/supabase/useSession";
|
import useSession from "@/lib/supabase/useSession";
|
||||||
import { getProjectByUserId } from "@/lib/data/projectQuery";
|
import { getProjectByUserId } from "@/lib/data/projectQuery";
|
||||||
import { Loader } from "@/components/loading/loader";
|
import { Loader } from "@/components/loading/loader";
|
||||||
|
import { getInvestmentByProjectsIds } from "@/lib/data/investmentQuery";
|
||||||
const data = [
|
import { useQuery } from "@supabase-cache-helpers/postgrest-react-query";
|
||||||
{
|
import { overAllGraphData, Deal, fourYearGraphData, dayOftheWeekData } from "../portfolio/[uid]/query";
|
||||||
name: "Jan",
|
import CountUp from "react-countup";
|
||||||
value: Math.floor(Math.random() * 5000) + 1000,
|
import { Button } from "@/components/ui/button";
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Feb",
|
|
||||||
value: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Mar",
|
|
||||||
value: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Apr",
|
|
||||||
value: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "May",
|
|
||||||
value: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Jun",
|
|
||||||
value: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Jul",
|
|
||||||
value: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Aug",
|
|
||||||
value: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Sep",
|
|
||||||
value: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Oct",
|
|
||||||
value: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Nov",
|
|
||||||
value: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "Dec",
|
|
||||||
value: Math.floor(Math.random() * 5000) + 1000,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export default function Dashboard() {
|
export default function Dashboard() {
|
||||||
let supabase = createSupabaseClient();
|
const supabase = createSupabaseClient();
|
||||||
const userId = useSession().session?.user.id;
|
const userId = useSession().session?.user.id;
|
||||||
const [projects, setProjects] = useState<
|
const [projects, setProjects] = useState<
|
||||||
{ id: number; project_name: string; business_id: { user_id: number }[]; dataroom_id: number }[]
|
{ id: number; project_name: string; business_id: { user_id: number }[]; dataroom_id: number }[]
|
||||||
>([]);
|
>([]);
|
||||||
|
const [latestInvestment, setLatestInvestment] = useState<
|
||||||
|
{
|
||||||
|
avatarUrl: string;
|
||||||
|
createdTime: Date;
|
||||||
|
dealAmount: number;
|
||||||
|
dealStatus: string;
|
||||||
|
investorId: string;
|
||||||
|
username: string;
|
||||||
|
}[]
|
||||||
|
>([]);
|
||||||
|
const tabOptions = ["daily", "monthly", "yearly"];
|
||||||
|
const [activeTab, setActiveTab] = useState("daily");
|
||||||
const [isSuccess, setIsSuccess] = useState(false);
|
const [isSuccess, setIsSuccess] = useState(false);
|
||||||
const [graphType, setGraphType] = useState("line");
|
const [graphType, setGraphType] = useState("line");
|
||||||
const dealList = useDealList();
|
const [currentProjectId, setCurrentProjectId] = useState<number>(projects[0]?.id);
|
||||||
const totalDealAmount = dealList?.reduce((sum, deal) => sum + deal.deal_amount, 0) || 0;
|
|
||||||
|
const investmentDetail = useQuery(
|
||||||
|
getInvestmentByProjectsIds(
|
||||||
|
supabase,
|
||||||
|
projects.map((item) => {
|
||||||
|
return item.id.toString();
|
||||||
|
})
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let graphData = [];
|
||||||
|
const filteredData = (investmentDetail?.data || []).filter((deal) => deal.project_id === currentProjectId);
|
||||||
|
const handleTabChange = (tab: string) => {
|
||||||
|
setActiveTab(tab);
|
||||||
|
};
|
||||||
|
|
||||||
|
if (activeTab === "daily") {
|
||||||
|
graphData = dayOftheWeekData(filteredData);
|
||||||
|
} else if (activeTab === "yearly") {
|
||||||
|
graphData = fourYearGraphData(filteredData);
|
||||||
|
} else {
|
||||||
|
graphData = overAllGraphData(filteredData);
|
||||||
|
}
|
||||||
|
useEffect(() => {
|
||||||
|
const setTopLatestInvestment = () => {
|
||||||
|
if (investmentDetail?.data) {
|
||||||
|
setLatestInvestment(
|
||||||
|
investmentDetail.data
|
||||||
|
.slice(0, 8)
|
||||||
|
.map((item) => {
|
||||||
|
// set the project according to current project id
|
||||||
|
if (item.project_id === currentProjectId) {
|
||||||
|
return {
|
||||||
|
avatarUrl: item.avatar_url,
|
||||||
|
createdTime: item.created_time,
|
||||||
|
dealAmount: item.deal_amount,
|
||||||
|
dealStatus: item.deal_status,
|
||||||
|
investorId: item.investor_id,
|
||||||
|
username: item.username,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
})
|
||||||
|
.filter((item) => item !== undefined) as {
|
||||||
|
avatarUrl: string;
|
||||||
|
createdTime: Date;
|
||||||
|
dealAmount: number;
|
||||||
|
dealStatus: string;
|
||||||
|
investorId: string;
|
||||||
|
username: string;
|
||||||
|
}[]
|
||||||
|
);
|
||||||
|
// console.table(latestInvestment);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
setTopLatestInvestment();
|
||||||
|
}, [supabase, investmentDetail]);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchProjects = async () => {
|
const fetchProjects = async () => {
|
||||||
if (userId) {
|
if (userId) {
|
||||||
const { data, error } = await getProjectByUserId(supabase, userId);
|
const { data, error } = await getProjectByUserId(supabase, userId);
|
||||||
// alert(JSON.stringify(data));
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error("Error while fetching projects");
|
console.error("Error while fetching projects");
|
||||||
}
|
}
|
||||||
if (data) {
|
if (data) {
|
||||||
|
// set project and current project id
|
||||||
setProjects(data);
|
setProjects(data);
|
||||||
// console.table(data);
|
setCurrentProjectId(data[0].id);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
console.error("Error with UserId while fetching projects");
|
console.error("Error with UserId while fetching projects");
|
||||||
@ -93,7 +113,7 @@ export default function Dashboard() {
|
|||||||
}, [supabase, userId]);
|
}, [supabase, userId]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="container max-w-screen-xl">
|
||||||
<Loader isSuccess={isSuccess} />
|
<Loader isSuccess={isSuccess} />
|
||||||
<div className="md:hidden">
|
<div className="md:hidden">
|
||||||
<Image
|
<Image
|
||||||
@ -116,148 +136,173 @@ export default function Dashboard() {
|
|||||||
<div className="flex items-center justify-between space-y-2">
|
<div className="flex items-center justify-between space-y-2">
|
||||||
<h2 className="text-3xl font-bold tracking-tight">Business Dashboard</h2>
|
<h2 className="text-3xl font-bold tracking-tight">Business Dashboard</h2>
|
||||||
</div>
|
</div>
|
||||||
<Tabs defaultValue={projects[0].project_name} className="space-y-4">
|
{projects && projects.length > 0 && (
|
||||||
<TabsList>
|
<Tabs className="space-y-4" defaultValue={projects[0].project_name}>
|
||||||
|
<TabsList>
|
||||||
|
{projects.map((project) => (
|
||||||
|
<TabsTrigger
|
||||||
|
key={project.id}
|
||||||
|
value={project.project_name}
|
||||||
|
onClick={() => setCurrentProjectId(project.id)}
|
||||||
|
>
|
||||||
|
{project.project_name}
|
||||||
|
</TabsTrigger>
|
||||||
|
))}
|
||||||
|
</TabsList>
|
||||||
{projects.map((project) => (
|
{projects.map((project) => (
|
||||||
<TabsTrigger key={project.id} value={project.project_name}>
|
<TabsContent value={project.project_name} className="space-y-4">
|
||||||
{project.project_name}
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||||
</TabsTrigger>
|
<Card>
|
||||||
))}
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
</TabsList>
|
<CardTitle className="text-sm font-medium">Total Funds Raised</CardTitle>
|
||||||
<TabsContent value={projects[0].project_name} className="space-y-4">
|
<svg
|
||||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<Card>
|
viewBox="0 0 24 24"
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
fill="none"
|
||||||
<CardTitle className="text-sm font-medium">Total Funds Raised</CardTitle>
|
stroke="currentColor"
|
||||||
<svg
|
strokeLinecap="round"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
strokeLinejoin="round"
|
||||||
viewBox="0 0 24 24"
|
strokeWidth="2"
|
||||||
fill="none"
|
className="h-4 w-4 text-muted-foreground"
|
||||||
stroke="currentColor"
|
>
|
||||||
strokeLinecap="round"
|
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
|
||||||
strokeLinejoin="round"
|
</svg>
|
||||||
strokeWidth="2"
|
</CardHeader>
|
||||||
className="h-4 w-4 text-muted-foreground"
|
<CardContent>
|
||||||
>
|
<div className="text-2xl font-bold">
|
||||||
<path d="M12 2v20M17 5H9.5a3.5 3.5 0 0 0 0 7h5a3.5 3.5 0 0 1 0 7H6" />
|
$
|
||||||
</svg>
|
<CountUp
|
||||||
</CardHeader>
|
end={filteredData
|
||||||
<CardContent>
|
.filter((project) => project.deal_status === "Completed")
|
||||||
<div className="text-2xl font-bold">${totalDealAmount}</div>
|
.reduce((sum, current) => sum + current.deal_amount, 0)}
|
||||||
{/* <p className="text-xs text-muted-foreground">
|
duration={1}
|
||||||
+20.1% from last month
|
/>
|
||||||
</p> */}
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-sm font-medium">Profile Views</CardTitle>
|
<CardTitle className="text-sm font-medium">Profile Views</CardTitle>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
className="h-4 w-4 text-muted-foreground"
|
className="h-4 w-4 text-muted-foreground"
|
||||||
>
|
>
|
||||||
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
<path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
|
||||||
<circle cx="12" cy="12" r="3"></circle>
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
</svg>
|
</svg>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">+2350</div>
|
<div className="text-2xl font-bold">
|
||||||
{/* <p className="text-xs text-muted-foreground">
|
+<CountUp end={2350} duration={1} />
|
||||||
|
</div>
|
||||||
|
{/* <p className="text-xs text-muted-foreground">
|
||||||
+180.1% from last month
|
+180.1% from last month
|
||||||
</p> */}
|
</p> */}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||||
<CardTitle className="text-sm font-medium">Total Followers</CardTitle>
|
<CardTitle className="text-sm font-medium">Total Followers</CardTitle>
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
fill="none"
|
fill="none"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
strokeWidth="2"
|
strokeWidth="2"
|
||||||
className="h-4 w-4 text-muted-foreground"
|
className="h-4 w-4 text-muted-foreground"
|
||||||
>
|
>
|
||||||
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2" />
|
||||||
<circle cx="9" cy="7" r="4" />
|
<circle cx="9" cy="7" r="4" />
|
||||||
<path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" />
|
<path d="M22 21v-2a4 4 0 0 0-3-3.87M16 3.13a4 4 0 0 1 0 7.75" />
|
||||||
</svg>
|
</svg>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<div className="text-2xl font-bold">+12,234</div>
|
<div className="text-2xl font-bold">
|
||||||
{/* <p className="text-xs text-muted-foreground">
|
+<CountUp end={12234} duration={1} />
|
||||||
|
</div>
|
||||||
|
{/* <p className="text-xs text-muted-foreground">
|
||||||
+19% from last month
|
+19% from last month
|
||||||
</p> */}
|
</p> */}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
{/* <Card>
|
<Button
|
||||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
onClick={() => {
|
||||||
<CardTitle className="text-sm font-medium">
|
window.location.href = `/project/${project.id}/edit`;
|
||||||
Active Now
|
}}
|
||||||
</CardTitle>
|
className="h-full bg-emerald-500 hover:bg-emerald-800 font-bold text-xl"
|
||||||
<svg
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeLinecap="round"
|
|
||||||
strokeLinejoin="round"
|
|
||||||
strokeWidth="2"
|
|
||||||
className="h-4 w-4 text-muted-foreground"
|
|
||||||
>
|
>
|
||||||
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
|
Edit Project
|
||||||
</svg>
|
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
</CardHeader>
|
<path
|
||||||
<CardContent>
|
d="M3 17.25V21h3.75l11.05-11.05-3.75-3.75L3 17.25zM20.71 7.04a1.003 1.003 0 000-1.42l-2.34-2.34a1.003 1.003 0 00-1.42 0L15.13 4.5l3.75 3.75 1.83-1.21z"
|
||||||
<div className="text-2xl font-bold">+573</div>
|
fill="currentColor"
|
||||||
<p className="text-xs text-muted-foreground">
|
/>
|
||||||
+201 since last hour
|
</svg>
|
||||||
</p>
|
</Button>
|
||||||
</CardContent>
|
</div>
|
||||||
</Card> */}
|
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
|
||||||
</div>
|
<Card className="col-span-4">
|
||||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
|
<CardHeader>
|
||||||
<Card className="col-span-4">
|
<CardTitle>Overview</CardTitle>
|
||||||
<CardHeader>
|
</CardHeader>
|
||||||
<CardTitle>Overview</CardTitle>
|
<Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
|
||||||
</CardHeader>
|
<TabsList className="grid w-56 grid-cols-3 ml-5">
|
||||||
<CardContent className="pl-2">
|
{tabOptions.map((tab) => (
|
||||||
<Overview graphType={graphType} data={data} />
|
<TabsTrigger key={tab} value={tab}>
|
||||||
{/* tab to switch between line and bar graph */}
|
{tab.charAt(0).toUpperCase() + tab.slice(1)}
|
||||||
<Tabs defaultValue="line" className="space-y-4 ml-[50%] mt-2">
|
</TabsTrigger>
|
||||||
<TabsList>
|
))}
|
||||||
<TabsTrigger value="line" onClick={() => setGraphType("line")}>
|
</TabsList>
|
||||||
Line
|
<CardContent className="pl-2 mt-5">
|
||||||
</TabsTrigger>
|
<Overview graphType={graphType} data={graphData} />
|
||||||
<TabsTrigger value="bar" onClick={() => setGraphType("bar")}>
|
|
||||||
Bar
|
<Tabs defaultValue="line" className="space-y-4 ml-[50%] mt-2">
|
||||||
</TabsTrigger>
|
<TabsList>
|
||||||
</TabsList>
|
<TabsTrigger value="line" onClick={() => setGraphType("line")}>
|
||||||
</Tabs>
|
Line
|
||||||
</CardContent>
|
</TabsTrigger>
|
||||||
</Card>
|
<TabsTrigger value="bar" onClick={() => setGraphType("bar")}>
|
||||||
<Card className="col-span-3">
|
Bar
|
||||||
<CardHeader>
|
</TabsTrigger>
|
||||||
<CardTitle>Recent Funds</CardTitle>
|
</TabsList>
|
||||||
<CardDescription>You made {dealList?.length || 0} sales this month.</CardDescription>
|
</Tabs>
|
||||||
</CardHeader>
|
</CardContent>
|
||||||
<CardContent>
|
</Tabs>
|
||||||
<RecentFunds></RecentFunds>
|
</Card>
|
||||||
</CardContent>
|
<Card className="col-span-3">
|
||||||
</Card>
|
<CardHeader>
|
||||||
</div>
|
<CardTitle>Recent Funds</CardTitle>
|
||||||
</TabsContent>
|
</CardHeader>
|
||||||
</Tabs>
|
<CardContent>
|
||||||
|
<RecentFunds
|
||||||
|
data={latestInvestment.map((item) => {
|
||||||
|
return {
|
||||||
|
name: item.username,
|
||||||
|
amount: item.dealAmount,
|
||||||
|
avatar: item.avatarUrl,
|
||||||
|
date: new Date(item.createdTime),
|
||||||
|
status: item.dealStatus,
|
||||||
|
profile_url: `/profile/${item.investorId}`,
|
||||||
|
};
|
||||||
|
})}
|
||||||
|
/>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
))}
|
||||||
|
</Tabs>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -52,7 +52,6 @@ export default async function Portfolio({ params }: { params: { uid: string } })
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
const username = localUser ? localUser.user.user_metadata.name : "Anonymous";
|
const username = localUser ? localUser.user.user_metadata.name : "Anonymous";
|
||||||
// console.log(username)
|
|
||||||
const overAllData = deals ? overAllGraphData(deals) : [];
|
const overAllData = deals ? overAllGraphData(deals) : [];
|
||||||
const fourYearData = deals ? fourYearGraphData(deals) : [];
|
const fourYearData = deals ? fourYearGraphData(deals) : [];
|
||||||
const dayOfWeekData = deals ? dayOftheWeekData(deals) : [];
|
const dayOfWeekData = deals ? dayOftheWeekData(deals) : [];
|
||||||
@ -66,31 +65,13 @@ export default async function Portfolio({ params }: { params: { uid: string } })
|
|||||||
)
|
)
|
||||||
: [];
|
: [];
|
||||||
const totalInvestment = deals ? getTotalInvestment(deals) : 0;
|
const totalInvestment = deals ? getTotalInvestment(deals) : 0;
|
||||||
// console.log(latestDeals);
|
|
||||||
|
|
||||||
const tagCount = countTags(tags);
|
const tagCount = countTags(tags);
|
||||||
// console.log(investedBusinessIds);
|
|
||||||
const businessType = deals
|
const businessType = deals
|
||||||
? await Promise.all(deals.map(async (item) => await getBusinessTypeName(supabase, item.project_id)))
|
? await Promise.all(deals.map(async (item) => await getBusinessTypeName(supabase, item.project_id)))
|
||||||
: [];
|
: [];
|
||||||
const countedBusinessType = countValues(businessType.filter((item) => item !== null));
|
const countedBusinessType = countValues(businessType.filter((item) => item !== null));
|
||||||
// console.log(countedBusinessType);
|
|
||||||
|
|
||||||
// console.log(tagCount);
|
|
||||||
return (
|
return (
|
||||||
<div className="container max-w-screen-xl">
|
<div className="container max-w-screen-xl">
|
||||||
{/* {JSON.stringify(params.uid)} */}
|
|
||||||
{/* {JSON.stringify(tagCount)} */}
|
|
||||||
{/* {JSON.stringify(deals)} */}
|
|
||||||
{/* {JSON.stringify(dayOfWeekData)} */}
|
|
||||||
{/* {JSON.stringify(overAllGraphData)} */}
|
|
||||||
{/* {JSON.stringify(threeYearGraphData)} */}
|
|
||||||
{/* {JSON.stringify(uniqueProjectIds)} */}
|
|
||||||
{/* <div className="flex flex-row">
|
|
||||||
<h1>Total Invest : </h1>
|
|
||||||
<div>{totalInvest}</div>
|
|
||||||
</div> */}
|
|
||||||
{/* <CountUpComponent end={100} duration={3} /> */}
|
|
||||||
<div className="text-center py-4">
|
<div className="text-center py-4">
|
||||||
<h1 className="text-2xl font-semibold">Welcome to your Portfolio, {username}!</h1>
|
<h1 className="text-2xl font-semibold">Welcome to your Portfolio, {username}!</h1>
|
||||||
<p className="text-lg text-muted-foreground">
|
<p className="text-lg text-muted-foreground">
|
||||||
|
|||||||
@ -26,11 +26,11 @@ function getTotalInvestment(deals: { deal_amount: number }[]) {
|
|||||||
}
|
}
|
||||||
async function getLatestInvestment(
|
async function getLatestInvestment(
|
||||||
supabase: SupabaseClient,
|
supabase: SupabaseClient,
|
||||||
deals: { project_id: number; deal_amount: number; created_time: Date }[]
|
deals: { project_id: number; deal_amount: number; created_time: Date;}[]
|
||||||
) {
|
) {
|
||||||
const llist = [];
|
const llist = [];
|
||||||
const count = 8;
|
const count = 8;
|
||||||
|
// select project name from the given id
|
||||||
for (let i = deals.length - 1; i >= 0 && llist.length < count; --i) {
|
for (let i = deals.length - 1; i >= 0 && llist.length < count; --i) {
|
||||||
let { data: project, error } = await supabase.from("project").select("project_name").eq("id", deals[i].project_id);
|
let { data: project, error } = await supabase.from("project").select("project_name").eq("id", deals[i].project_id);
|
||||||
if (error) {
|
if (error) {
|
||||||
@ -38,6 +38,7 @@ async function getLatestInvestment(
|
|||||||
}
|
}
|
||||||
let url = fetchLogoURL(supabase, deals[i].project_id);
|
let url = fetchLogoURL(supabase, deals[i].project_id);
|
||||||
llist.push({
|
llist.push({
|
||||||
|
projectId: deals[i].project_id,
|
||||||
name: project?.[0]?.project_name,
|
name: project?.[0]?.project_name,
|
||||||
amount: deals[i].deal_amount,
|
amount: deals[i].deal_amount,
|
||||||
date: new Date(deals[i].created_time),
|
date: new Date(deals[i].created_time),
|
||||||
@ -104,7 +105,7 @@ async function getBusinessTypeName(supabase: SupabaseClient, projectId: number)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// only use deal that were made at most year ago
|
// only use deal that were made at most year ago
|
||||||
interface Deal {
|
export interface Deal {
|
||||||
created_time: string | number | Date;
|
created_time: string | number | Date;
|
||||||
deal_amount: any;
|
deal_amount: any;
|
||||||
}
|
}
|
||||||
|
|||||||
88
src/components/carousel.tsx
Normal file
88
src/components/carousel.tsx
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
"use client";
|
||||||
|
import { useEffect, useState, useMemo } from "react";
|
||||||
|
import { Carousel, CarouselContent, CarouselItem, type CarouselApi } from "./ui/carousel";
|
||||||
|
import Image from "next/image";
|
||||||
|
|
||||||
|
interface GalleryProps {
|
||||||
|
images: { src: string }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const Gallery = ({ images }: GalleryProps) => {
|
||||||
|
const [mainApi, setMainApi] = useState<CarouselApi>();
|
||||||
|
const [thumbnailApi, setThumbnailApi] = useState<CarouselApi>();
|
||||||
|
const [current, setCurrent] = useState(0);
|
||||||
|
|
||||||
|
const mainImage = useMemo(
|
||||||
|
() =>
|
||||||
|
images.map((image, index) => (
|
||||||
|
<CarouselItem key={index} className="relative aspect-video w-full border-8 border-b">
|
||||||
|
<Image src={image.src} alt={`Carousel Main Image ${index + 1}`} fill style={{ objectFit: "contain" }} />
|
||||||
|
</CarouselItem>
|
||||||
|
)),
|
||||||
|
[images]
|
||||||
|
);
|
||||||
|
|
||||||
|
const thumbnailImages = useMemo(
|
||||||
|
() =>
|
||||||
|
images.map((image, index) => (
|
||||||
|
<CarouselItem key={index} className="relative aspect-square basis-1/4" onClick={() => handleClick(index)}>
|
||||||
|
<Image
|
||||||
|
className={`${index === current ? "border-2" : ""}`}
|
||||||
|
src={image.src}
|
||||||
|
fill
|
||||||
|
alt={`Carousel Thumbnail Image ${index + 1}`}
|
||||||
|
style={{ objectFit: "contain" }}
|
||||||
|
/>
|
||||||
|
</CarouselItem>
|
||||||
|
)),
|
||||||
|
[images, current]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!mainApi || !thumbnailApi) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleTopSelect = () => {
|
||||||
|
const selected = mainApi.selectedScrollSnap();
|
||||||
|
setCurrent(selected);
|
||||||
|
thumbnailApi.scrollTo(selected);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBottomSelect = () => {
|
||||||
|
const selected = thumbnailApi.selectedScrollSnap();
|
||||||
|
setCurrent(selected);
|
||||||
|
mainApi.scrollTo(selected);
|
||||||
|
};
|
||||||
|
|
||||||
|
mainApi.on("select", handleTopSelect);
|
||||||
|
thumbnailApi.on("select", handleBottomSelect);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
mainApi.off("select", handleTopSelect);
|
||||||
|
thumbnailApi.off("select", handleBottomSelect);
|
||||||
|
};
|
||||||
|
}, [mainApi, thumbnailApi]);
|
||||||
|
|
||||||
|
const handleClick = (index: number) => {
|
||||||
|
if (!mainApi || !thumbnailApi) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
thumbnailApi.scrollTo(index);
|
||||||
|
mainApi.scrollTo(index);
|
||||||
|
setCurrent(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full max-w-xl sm:w-auto">
|
||||||
|
<Carousel setApi={setMainApi}>
|
||||||
|
<CarouselContent className="m-1">{mainImage}</CarouselContent>
|
||||||
|
</Carousel>
|
||||||
|
<Carousel setApi={setThumbnailApi}>
|
||||||
|
<CarouselContent className="m-1 h-16">{thumbnailImages}</CarouselContent>
|
||||||
|
</Carousel>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Gallery;
|
||||||
@ -71,15 +71,19 @@ export function ProjectCard(props: ProjectCardProps) {
|
|||||||
<span className="text-xs">{props.location}</span>
|
<span className="text-xs">{props.location}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap mt-1 items-center text-muted-foreground">
|
<div className="flex flex-wrap mt-1 items-center text-muted-foreground">
|
||||||
{props.tags && Array.isArray(props.tags) ? (
|
{props.tags?.length !== 0 && Array.isArray(props.tags)
|
||||||
props.tags.map((tag) => (
|
? props.tags
|
||||||
<span id="tag" key={tag} className="text-[10px] rounded-md bg-slate-200 dark:bg-slate-700 p-1 mr-1">
|
.filter((tag) => tag && tag.trim() !== "") // Filters out null or blank tags
|
||||||
{tag}
|
.map((tag) => (
|
||||||
</span>
|
<span
|
||||||
))
|
id="tag"
|
||||||
) : (
|
key={tag}
|
||||||
<span className="text-xs text-muted-foreground">No tags available</span>
|
className="text-[10px] rounded-md bg-slate-200 dark:bg-slate-700 p-1 mr-1"
|
||||||
)}
|
>
|
||||||
|
{tag}
|
||||||
|
</span>
|
||||||
|
))
|
||||||
|
: null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export type RecentDealData = {
|
export type RecentDealData = {
|
||||||
created_time: Date;
|
created_time: Date;
|
||||||
@ -6,29 +7,127 @@ export type RecentDealData = {
|
|||||||
investor_id: string;
|
investor_id: string;
|
||||||
username: string;
|
username: string;
|
||||||
logo_url?: string;
|
logo_url?: string;
|
||||||
|
status?: string;
|
||||||
// email: string;
|
// email: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface RecentFundsProps {
|
interface RecentFundsProps {
|
||||||
data?: { name?: string; amount?: number; avatar?: string; date?: Date; logo_url?: string }[];
|
data?: { name?: string; amount?: number; avatar?: string; date?: Date; logo_url?: string; status?: string; profile_url?: string }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RecentFunds(props: RecentFundsProps) {
|
export function RecentFunds(props: RecentFundsProps) {
|
||||||
|
const content = (
|
||||||
|
<div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
)
|
||||||
return (
|
return (
|
||||||
<div className="space-y-8">
|
<div className="space-y-8">
|
||||||
{(props?.data || []).map((deal, index) => (
|
{(props?.data || []).map((deal, index) => (
|
||||||
<div className="flex items-center" key={index}>
|
<div key={index}>
|
||||||
<Avatar className="h-9 w-9">
|
{deal.profile_url ? (
|
||||||
<AvatarImage src={deal.logo_url} alt={deal.name} />
|
<Link
|
||||||
<AvatarFallback>{(deal.name ?? "").slice(0, 2)}</AvatarFallback>
|
href={deal.profile_url}
|
||||||
</Avatar>
|
className="flex items-center w-full"
|
||||||
<div className="ml-4 space-y-1">
|
target="_blank"
|
||||||
<p className="text-sm font-medium leading-none">{deal.name}</p>
|
rel="noopener noreferrer"
|
||||||
<p className="text-xs text-muted-foreground">{deal?.date?.toLocaleDateString()}</p>
|
>
|
||||||
</div>
|
<Avatar className="h-9 w-9">
|
||||||
<div className="ml-auto font-medium">+${deal.amount}</div>
|
<AvatarImage src={deal.logo_url ? deal.logo_url : deal.avatar} alt={deal.name} />
|
||||||
|
<AvatarFallback>{(deal.name ?? "").slice(0, 2)}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="ml-4 space-y-1">
|
||||||
|
<p className="text-sm font-medium leading-none">{deal.name}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">{deal?.date?.toLocaleDateString()}</p>
|
||||||
|
{deal.status && (
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
<span className="relative flex h-3 w-3">
|
||||||
|
<span
|
||||||
|
className={`animate-ping absolute inline-flex h-3 w-3 rounded-full opacity-75 ${
|
||||||
|
deal?.status === "In Progress"
|
||||||
|
? "bg-sky-400"
|
||||||
|
: deal?.status === "Completed"
|
||||||
|
? "bg-green-400"
|
||||||
|
: "bg-yellow-400"
|
||||||
|
}`}
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
className={`relative inline-flex rounded-full h-2 w-2 mt-[2px] ml-0.5 ${
|
||||||
|
deal?.status === "In Progress"
|
||||||
|
? "bg-sky-500"
|
||||||
|
: deal?.status === "Completed"
|
||||||
|
? "bg-green-500"
|
||||||
|
: "bg-yellow-500"
|
||||||
|
}`}
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
|
<p
|
||||||
|
className={`text-xs m-0 ${
|
||||||
|
deal?.status === "In Progress"
|
||||||
|
? "text-sky-500"
|
||||||
|
: deal?.status === "Completed"
|
||||||
|
? "text-green-500"
|
||||||
|
: "text-yellow-500"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{deal?.status}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto font-medium">+${deal.amount}</div>
|
||||||
|
</Link>
|
||||||
|
) : (
|
||||||
|
<div className="flex items-center w-full">
|
||||||
|
<Avatar className="h-9 w-9">
|
||||||
|
<AvatarImage src={deal.logo_url ? deal.logo_url : deal.avatar} alt={deal.name} />
|
||||||
|
<AvatarFallback>{(deal.name ?? "").slice(0, 2)}</AvatarFallback>
|
||||||
|
</Avatar>
|
||||||
|
<div className="ml-4 space-y-1">
|
||||||
|
<p className="text-sm font-medium leading-none">{deal.name}</p>
|
||||||
|
<p className="text-xs text-muted-foreground">{deal?.date?.toLocaleDateString()}</p>
|
||||||
|
{deal.status && (
|
||||||
|
<div className="flex items-center space-x-1">
|
||||||
|
<span className="relative flex h-3 w-3">
|
||||||
|
<span
|
||||||
|
className={`animate-ping absolute inline-flex h-3 w-3 rounded-full opacity-75 ${
|
||||||
|
deal?.status === "In Progress"
|
||||||
|
? "bg-sky-400"
|
||||||
|
: deal?.status === "Completed"
|
||||||
|
? "bg-green-400"
|
||||||
|
: "bg-yellow-400"
|
||||||
|
}`}
|
||||||
|
></span>
|
||||||
|
<span
|
||||||
|
className={`relative inline-flex rounded-full h-2 w-2 mt-[2px] ml-0.5 ${
|
||||||
|
deal?.status === "In Progress"
|
||||||
|
? "bg-sky-500"
|
||||||
|
: deal?.status === "Completed"
|
||||||
|
? "bg-green-500"
|
||||||
|
: "bg-yellow-500"
|
||||||
|
}`}
|
||||||
|
></span>
|
||||||
|
</span>
|
||||||
|
<p
|
||||||
|
className={`text-xs m-0 ${
|
||||||
|
deal?.status === "In Progress"
|
||||||
|
? "text-sky-500"
|
||||||
|
: deal?.status === "Completed"
|
||||||
|
? "text-green-500"
|
||||||
|
: "text-yellow-500"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{deal?.status}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="ml-auto font-medium">+${deal.amount}</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,6 +10,29 @@ export const getInvestmentCountsByProjectsIds = (client: SupabaseClient, project
|
|||||||
.in("project_id", projectIds);
|
.in("project_id", projectIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getInvestmentByProjectsIds = (client: SupabaseClient, projectIds: string[]) => {
|
||||||
|
return client
|
||||||
|
.from("investment_deal")
|
||||||
|
.select(
|
||||||
|
`
|
||||||
|
id,
|
||||||
|
...deal_status_id(
|
||||||
|
deal_status:value
|
||||||
|
),
|
||||||
|
project_id,
|
||||||
|
deal_amount,
|
||||||
|
created_time,
|
||||||
|
...profiles (
|
||||||
|
investor_id:id,
|
||||||
|
username,
|
||||||
|
avatar_url
|
||||||
|
)
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.in("project_id", projectIds)
|
||||||
|
.order("created_time", { ascending: false });
|
||||||
|
};
|
||||||
|
|
||||||
export const getInvestmentByUserId = (client: SupabaseClient, userId: string) => {
|
export const getInvestmentByUserId = (client: SupabaseClient, userId: string) => {
|
||||||
return client
|
return client
|
||||||
.from("investment_deal")
|
.from("investment_deal")
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user