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 { 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 { Progress } from "@/components/ui/progress";
|
||||
import { Separator } from "@/components/ui/separator";
|
||||
import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
|
||||
import FollowShareButtons from "./followShareButton";
|
||||
import { DisplayFullImage } from "./displayImage";
|
||||
|
||||
import { getProjectData } from "@/lib/data/projectQuery";
|
||||
import { getDealList } from "@/app/api/dealApi";
|
||||
import { sumByKey, toPercentage } from "@/lib/utils";
|
||||
@ -19,6 +18,7 @@ import { redirect } from "next/navigation";
|
||||
import { isOwnerOfProject } from "./query";
|
||||
import { UpdateTab } from "./UpdateTab";
|
||||
import remarkGfm from "remark-gfm";
|
||||
import Gallery from "@/components/carousel";
|
||||
|
||||
const PHOTO_MATERIAL_ID = 2;
|
||||
|
||||
@ -75,7 +75,6 @@ export default async function ProjectDealPage({ params }: { params: { id: number
|
||||
? projectMaterial.flatMap((item) =>
|
||||
(item.material_url || ["/boiler1.jpg"]).map((url: string) => ({
|
||||
src: url,
|
||||
alt: "Image",
|
||||
}))
|
||||
)
|
||||
: [{ src: "/boiler1.jpg", alt: "Default Boiler Image" }];
|
||||
@ -104,35 +103,8 @@ export default async function ProjectDealPage({ params }: { params: { id: number
|
||||
</div>
|
||||
<div id="sub-content" className="flex flex-row mt-5">
|
||||
{/* image carousel */}
|
||||
<div id="image-carousel" className="shrink-0 w-[700px] flex flex-col">
|
||||
{/* first carousel */}
|
||||
<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 id="image-carousel" className="w-full">
|
||||
<Gallery images={carouselData} />
|
||||
</div>
|
||||
|
||||
<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 { RecentFunds } from "@/components/recent-funds";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useDealList } from "./hook";
|
||||
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
|
||||
import useSession from "@/lib/supabase/useSession";
|
||||
import { getProjectByUserId } from "@/lib/data/projectQuery";
|
||||
import { Loader } from "@/components/loading/loader";
|
||||
|
||||
const data = [
|
||||
{
|
||||
name: "Jan",
|
||||
value: Math.floor(Math.random() * 5000) + 1000,
|
||||
},
|
||||
{
|
||||
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,
|
||||
},
|
||||
];
|
||||
import { getInvestmentByProjectsIds } from "@/lib/data/investmentQuery";
|
||||
import { useQuery } from "@supabase-cache-helpers/postgrest-react-query";
|
||||
import { overAllGraphData, Deal, fourYearGraphData, dayOftheWeekData } from "../portfolio/[uid]/query";
|
||||
import CountUp from "react-countup";
|
||||
import { Button } from "@/components/ui/button";
|
||||
|
||||
export default function Dashboard() {
|
||||
let supabase = createSupabaseClient();
|
||||
const supabase = createSupabaseClient();
|
||||
const userId = useSession().session?.user.id;
|
||||
const [projects, setProjects] = useState<
|
||||
{ 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 [graphType, setGraphType] = useState("line");
|
||||
const dealList = useDealList();
|
||||
const totalDealAmount = dealList?.reduce((sum, deal) => sum + deal.deal_amount, 0) || 0;
|
||||
const [currentProjectId, setCurrentProjectId] = useState<number>(projects[0]?.id);
|
||||
|
||||
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(() => {
|
||||
const fetchProjects = async () => {
|
||||
if (userId) {
|
||||
const { data, error } = await getProjectByUserId(supabase, userId);
|
||||
// alert(JSON.stringify(data));
|
||||
if (error) {
|
||||
console.error("Error while fetching projects");
|
||||
}
|
||||
if (data) {
|
||||
// set project and current project id
|
||||
setProjects(data);
|
||||
// console.table(data);
|
||||
setCurrentProjectId(data[0].id);
|
||||
}
|
||||
} else {
|
||||
console.error("Error with UserId while fetching projects");
|
||||
@ -93,7 +113,7 @@ export default function Dashboard() {
|
||||
}, [supabase, userId]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="container max-w-screen-xl">
|
||||
<Loader isSuccess={isSuccess} />
|
||||
<div className="md:hidden">
|
||||
<Image
|
||||
@ -116,15 +136,21 @@ export default function Dashboard() {
|
||||
<div className="flex items-center justify-between space-y-2">
|
||||
<h2 className="text-3xl font-bold tracking-tight">Business Dashboard</h2>
|
||||
</div>
|
||||
<Tabs defaultValue={projects[0].project_name} className="space-y-4">
|
||||
{projects && projects.length > 0 && (
|
||||
<Tabs className="space-y-4" defaultValue={projects[0].project_name}>
|
||||
<TabsList>
|
||||
{projects.map((project) => (
|
||||
<TabsTrigger key={project.id} value={project.project_name}>
|
||||
<TabsTrigger
|
||||
key={project.id}
|
||||
value={project.project_name}
|
||||
onClick={() => setCurrentProjectId(project.id)}
|
||||
>
|
||||
{project.project_name}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
<TabsContent value={projects[0].project_name} className="space-y-4">
|
||||
{projects.map((project) => (
|
||||
<TabsContent value={project.project_name} className="space-y-4">
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-4">
|
||||
<Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
@ -143,10 +169,15 @@ export default function Dashboard() {
|
||||
</svg>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">${totalDealAmount}</div>
|
||||
{/* <p className="text-xs text-muted-foreground">
|
||||
+20.1% from last month
|
||||
</p> */}
|
||||
<div className="text-2xl font-bold">
|
||||
$
|
||||
<CountUp
|
||||
end={filteredData
|
||||
.filter((project) => project.deal_status === "Completed")
|
||||
.reduce((sum, current) => sum + current.deal_amount, 0)}
|
||||
duration={1}
|
||||
/>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card>
|
||||
@ -167,7 +198,9 @@ export default function Dashboard() {
|
||||
</svg>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">+2350</div>
|
||||
<div className="text-2xl font-bold">
|
||||
+<CountUp end={2350} duration={1} />
|
||||
</div>
|
||||
{/* <p className="text-xs text-muted-foreground">
|
||||
+180.1% from last month
|
||||
</p> */}
|
||||
@ -192,46 +225,45 @@ export default function Dashboard() {
|
||||
</svg>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">+12,234</div>
|
||||
<div className="text-2xl font-bold">
|
||||
+<CountUp end={12234} duration={1} />
|
||||
</div>
|
||||
{/* <p className="text-xs text-muted-foreground">
|
||||
+19% from last month
|
||||
</p> */}
|
||||
</CardContent>
|
||||
</Card>
|
||||
{/* <Card>
|
||||
<CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
|
||||
<CardTitle className="text-sm font-medium">
|
||||
Active Now
|
||||
</CardTitle>
|
||||
<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"
|
||||
<Button
|
||||
onClick={() => {
|
||||
window.location.href = `/project/${project.id}/edit`;
|
||||
}}
|
||||
className="h-full bg-emerald-500 hover:bg-emerald-800 font-bold text-xl"
|
||||
>
|
||||
<path d="M22 12h-4l-3 9L9 3l-3 9H2" />
|
||||
Edit Project
|
||||
<svg width="32" height="32" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
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"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</svg>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="text-2xl font-bold">+573</div>
|
||||
<p className="text-xs text-muted-foreground">
|
||||
+201 since last hour
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card> */}
|
||||
</Button>
|
||||
</div>
|
||||
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-7">
|
||||
<Card className="col-span-4">
|
||||
<CardHeader>
|
||||
<CardTitle>Overview</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="pl-2">
|
||||
<Overview graphType={graphType} data={data} />
|
||||
{/* tab to switch between line and bar graph */}
|
||||
<Tabs value={activeTab} onValueChange={handleTabChange} className="w-full">
|
||||
<TabsList className="grid w-56 grid-cols-3 ml-5">
|
||||
{tabOptions.map((tab) => (
|
||||
<TabsTrigger key={tab} value={tab}>
|
||||
{tab.charAt(0).toUpperCase() + tab.slice(1)}
|
||||
</TabsTrigger>
|
||||
))}
|
||||
</TabsList>
|
||||
<CardContent className="pl-2 mt-5">
|
||||
<Overview graphType={graphType} data={graphData} />
|
||||
|
||||
<Tabs defaultValue="line" className="space-y-4 ml-[50%] mt-2">
|
||||
<TabsList>
|
||||
<TabsTrigger value="line" onClick={() => setGraphType("line")}>
|
||||
@ -243,21 +275,34 @@ export default function Dashboard() {
|
||||
</TabsList>
|
||||
</Tabs>
|
||||
</CardContent>
|
||||
</Tabs>
|
||||
</Card>
|
||||
<Card className="col-span-3">
|
||||
<CardHeader>
|
||||
<CardTitle>Recent Funds</CardTitle>
|
||||
<CardDescription>You made {dealList?.length || 0} sales this month.</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<RecentFunds></RecentFunds>
|
||||
<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>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@ -52,7 +52,6 @@ export default async function Portfolio({ params }: { params: { uid: string } })
|
||||
);
|
||||
}
|
||||
const username = localUser ? localUser.user.user_metadata.name : "Anonymous";
|
||||
// console.log(username)
|
||||
const overAllData = deals ? overAllGraphData(deals) : [];
|
||||
const fourYearData = deals ? fourYearGraphData(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;
|
||||
// console.log(latestDeals);
|
||||
|
||||
const tagCount = countTags(tags);
|
||||
// console.log(investedBusinessIds);
|
||||
const businessType = deals
|
||||
? await Promise.all(deals.map(async (item) => await getBusinessTypeName(supabase, item.project_id)))
|
||||
: [];
|
||||
const countedBusinessType = countValues(businessType.filter((item) => item !== null));
|
||||
// console.log(countedBusinessType);
|
||||
|
||||
// console.log(tagCount);
|
||||
return (
|
||||
<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">
|
||||
<h1 className="text-2xl font-semibold">Welcome to your Portfolio, {username}!</h1>
|
||||
<p className="text-lg text-muted-foreground">
|
||||
|
||||
@ -26,11 +26,11 @@ function getTotalInvestment(deals: { deal_amount: number }[]) {
|
||||
}
|
||||
async function getLatestInvestment(
|
||||
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 count = 8;
|
||||
|
||||
// select project name from the given id
|
||||
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);
|
||||
if (error) {
|
||||
@ -38,6 +38,7 @@ async function getLatestInvestment(
|
||||
}
|
||||
let url = fetchLogoURL(supabase, deals[i].project_id);
|
||||
llist.push({
|
||||
projectId: deals[i].project_id,
|
||||
name: project?.[0]?.project_name,
|
||||
amount: deals[i].deal_amount,
|
||||
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
|
||||
interface Deal {
|
||||
export interface Deal {
|
||||
created_time: string | number | Date;
|
||||
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>
|
||||
</div>
|
||||
<div className="flex flex-wrap mt-1 items-center text-muted-foreground">
|
||||
{props.tags && Array.isArray(props.tags) ? (
|
||||
props.tags.map((tag) => (
|
||||
<span id="tag" key={tag} className="text-[10px] rounded-md bg-slate-200 dark:bg-slate-700 p-1 mr-1">
|
||||
{props.tags?.length !== 0 && Array.isArray(props.tags)
|
||||
? props.tags
|
||||
.filter((tag) => tag && tag.trim() !== "") // Filters out null or blank tags
|
||||
.map((tag) => (
|
||||
<span
|
||||
id="tag"
|
||||
key={tag}
|
||||
className="text-[10px] rounded-md bg-slate-200 dark:bg-slate-700 p-1 mr-1"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
))
|
||||
) : (
|
||||
<span className="text-xs text-muted-foreground">No tags available</span>
|
||||
)}
|
||||
: null}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
||||
import Link from "next/link";
|
||||
|
||||
export type RecentDealData = {
|
||||
created_time: Date;
|
||||
@ -6,29 +7,127 @@ export type RecentDealData = {
|
||||
investor_id: string;
|
||||
username: string;
|
||||
logo_url?: string;
|
||||
status?: string;
|
||||
// email: string;
|
||||
};
|
||||
|
||||
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) {
|
||||
const content = (
|
||||
<div>
|
||||
|
||||
</div>
|
||||
)
|
||||
return (
|
||||
<div className="space-y-8">
|
||||
{(props?.data || []).map((deal, index) => (
|
||||
<div className="flex items-center" key={index}>
|
||||
<div key={index}>
|
||||
{deal.profile_url ? (
|
||||
<Link
|
||||
href={deal.profile_url}
|
||||
className="flex items-center w-full"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<Avatar className="h-9 w-9">
|
||||
<AvatarImage src={deal.logo_url} alt={deal.name} />
|
||||
<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>
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
@ -10,6 +10,29 @@ export const getInvestmentCountsByProjectsIds = (client: SupabaseClient, project
|
||||
.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) => {
|
||||
return client
|
||||
.from("investment_deal")
|
||||
|
||||
Loading…
Reference in New Issue
Block a user