mirror of
https://github.com/Sosokker/B2D-Ventures.git
synced 2025-12-20 06:24:06 +01:00
feat: add admin page for project application
This commit is contained in:
parent
76a288f509
commit
1c58bbc8cf
104
src/app/admin/project/[businessId]/ProjectAction.tsx
Normal file
104
src/app/admin/project/[businessId]/ProjectAction.tsx
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import {
|
||||||
|
Dialog,
|
||||||
|
DialogContent,
|
||||||
|
DialogDescription,
|
||||||
|
DialogFooter,
|
||||||
|
DialogHeader,
|
||||||
|
DialogTitle,
|
||||||
|
DialogTrigger,
|
||||||
|
} from "@/components/ui/dialog";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Check, X } from "lucide-react";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { createSupabaseClient } from "@/lib/supabase/clientComponentClient";
|
||||||
|
import { approveProject, rejectBusiness } from "@/lib/data/applicationMutate";
|
||||||
|
import toast from "react-hot-toast";
|
||||||
|
|
||||||
|
interface ProjectActionsProps {
|
||||||
|
projectId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function ProjectActions({ projectId }: ProjectActionsProps) {
|
||||||
|
const [isRejectLoading, setIsRejectLoading] = useState(false);
|
||||||
|
const [isApproveLoading, setIsApproveLoading] = useState(false);
|
||||||
|
const [isRejectOpen, setIsRejectOpen] = useState(false);
|
||||||
|
const [isApproveOpen, setIsApproveOpen] = useState(false);
|
||||||
|
|
||||||
|
const onRejectProject = async () => {
|
||||||
|
try {
|
||||||
|
setIsRejectLoading(true);
|
||||||
|
const client = createSupabaseClient();
|
||||||
|
|
||||||
|
const { error } = await rejectBusiness(client, projectId);
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
toast.success("Project rejected successfully");
|
||||||
|
window.location.reload();
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Failed to reject project");
|
||||||
|
console.error("Failed to reject project:", error);
|
||||||
|
} finally {
|
||||||
|
setIsRejectLoading(false);
|
||||||
|
setIsRejectOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onApproveProject = async () => {
|
||||||
|
try {
|
||||||
|
setIsApproveLoading(true);
|
||||||
|
const client = createSupabaseClient();
|
||||||
|
|
||||||
|
const { error } = await approveProject(client, projectId);
|
||||||
|
if (error) throw error;
|
||||||
|
|
||||||
|
toast.success("Project approved successfully");
|
||||||
|
window.location.reload();
|
||||||
|
} catch (error) {
|
||||||
|
toast.error("Failed to approve project");
|
||||||
|
console.error("Failed to approve project:", error);
|
||||||
|
} finally {
|
||||||
|
setIsApproveLoading(false);
|
||||||
|
setIsApproveOpen(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1">
|
||||||
|
<Dialog open={isApproveOpen} onOpenChange={setIsApproveOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<Check className="border-[2px] border-black dark:border-white rounded-md hover:bg-primary cursor-pointer" />
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Approve this Project</DialogTitle>
|
||||||
|
<DialogDescription>Are you sure you want to approve this project?</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="submit" variant="default" onClick={onApproveProject} disabled={isApproveLoading}>
|
||||||
|
{isApproveLoading ? "Approving..." : "Approve"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<Dialog open={isRejectOpen} onOpenChange={setIsRejectOpen}>
|
||||||
|
<DialogTrigger asChild>
|
||||||
|
<X className="border-[2px] border-black dark:border-white rounded-md hover:bg-destructive cursor-pointer" />
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent className="sm:max-w-[425px]">
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Reject this Project</DialogTitle>
|
||||||
|
<DialogDescription>Are you sure you want to reject this project?</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button type="submit" variant="destructive" onClick={onRejectProject} disabled={isRejectLoading}>
|
||||||
|
{isRejectLoading ? "Rejecting..." : "Reject"}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
139
src/app/admin/project/[businessId]/page.tsx
Normal file
139
src/app/admin/project/[businessId]/page.tsx
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { createSupabaseClient } from "@/lib/supabase/serverComponentClient";
|
||||||
|
import { getAllProjectApplicationByBusinessQuery } from "@/lib/data/applicationQuery";
|
||||||
|
import Link from "next/link";
|
||||||
|
import Image from "next/image";
|
||||||
|
import ProjectActions from "./ProjectAction";
|
||||||
|
import { Table, TableBody, TableCaption, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
|
||||||
|
|
||||||
|
interface ProjectApplicationData {
|
||||||
|
id: number;
|
||||||
|
created_at: string;
|
||||||
|
deadline: string;
|
||||||
|
status: string;
|
||||||
|
project_name: string;
|
||||||
|
business_id: number;
|
||||||
|
business_name: string;
|
||||||
|
user_id: number;
|
||||||
|
project_type_id: number;
|
||||||
|
project_type_value: string;
|
||||||
|
short_description: string;
|
||||||
|
pitch_deck_url: string | null;
|
||||||
|
project_logo: string | null;
|
||||||
|
min_investment: number;
|
||||||
|
target_investment: number;
|
||||||
|
project_photos: string[] | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProjectApplicationTableProps {
|
||||||
|
projects: ProjectApplicationData[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ProjectApplicationTable({ projects }: ProjectApplicationTableProps) {
|
||||||
|
if (!projects || projects.length === 0) {
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={11} className="text-center h-24 text-muted-foreground">
|
||||||
|
No project applications found
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{projects.map((project) => (
|
||||||
|
<TableRow key={project.id}>
|
||||||
|
<TableCell>{project.project_name}</TableCell>
|
||||||
|
<TableCell>{project.project_type_value}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{project.project_logo && (
|
||||||
|
<Image
|
||||||
|
src={project.project_logo}
|
||||||
|
alt={`${project.project_name} logo`}
|
||||||
|
width={40}
|
||||||
|
height={40}
|
||||||
|
className="rounded-md"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{project.short_description}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{project.pitch_deck_url && (
|
||||||
|
<Link href={project.pitch_deck_url} className="text-blue-500 hover:text-blue-600">
|
||||||
|
Pitch Deck
|
||||||
|
</Link>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{project.min_investment}</TableCell>
|
||||||
|
<TableCell>{project.target_investment}</TableCell>
|
||||||
|
<TableCell>{new Date(project.created_at).toLocaleDateString()}</TableCell>
|
||||||
|
<TableCell>{new Date(project.deadline).toLocaleDateString()}</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
{project.project_photos ? (
|
||||||
|
project.project_photos.length > 0 && (
|
||||||
|
<div className="flex space-x-2">
|
||||||
|
{project.project_photos.map((photoUrl, index) => (
|
||||||
|
<Image
|
||||||
|
key={index}
|
||||||
|
src={photoUrl}
|
||||||
|
alt={`Project photo ${index + 1}`}
|
||||||
|
width={40}
|
||||||
|
height={40}
|
||||||
|
className="rounded-md"
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
) : (
|
||||||
|
<p>No images available</p>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<ProjectActions projectId={project.id} />
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function ProjectAdminPage({ params }: { params: { businessId: string } }) {
|
||||||
|
const client = createSupabaseClient();
|
||||||
|
const { data: projectApplicationData, error: projectApplicationError } =
|
||||||
|
await getAllProjectApplicationByBusinessQuery(client, Number(params.businessId));
|
||||||
|
|
||||||
|
if (projectApplicationError) {
|
||||||
|
console.log(projectApplicationError);
|
||||||
|
return <div>Error loading project applications</div>;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="container max-w-screen-xl my-4">
|
||||||
|
<h1 className="text-2xl font-semibold mb-4">
|
||||||
|
{projectApplicationData?.[0]?.business_name || "Business Projects"}
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<Table className="border-2 border-border rounded-md">
|
||||||
|
<TableCaption>Project Applications for Business</TableCaption>
|
||||||
|
<TableHeader>
|
||||||
|
<TableRow>
|
||||||
|
<TableHead>Project Name</TableHead>
|
||||||
|
<TableHead>Project Type</TableHead>
|
||||||
|
<TableHead>Logo</TableHead>
|
||||||
|
<TableHead>Description</TableHead>
|
||||||
|
<TableHead>Pitch Deck</TableHead>
|
||||||
|
<TableHead>Min Investment</TableHead>
|
||||||
|
<TableHead>Target Investment</TableHead>
|
||||||
|
<TableHead>Created At</TableHead>
|
||||||
|
<TableHead>Deadline</TableHead>
|
||||||
|
<TableHead>Photos</TableHead>
|
||||||
|
<TableHead>Actions</TableHead>
|
||||||
|
</TableRow>
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
<ProjectApplicationTable projects={projectApplicationData || []} />
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -1,23 +1,37 @@
|
|||||||
import { SupabaseClient } from "@supabase/supabase-js";
|
import { SupabaseClient } from "@supabase/supabase-js";
|
||||||
|
|
||||||
export async function rejectBusiness(
|
export async function rejectBusiness(client: SupabaseClient, businessApplicationId: Number) {
|
||||||
client: SupabaseClient,
|
return client
|
||||||
businessApplicationId: Number,
|
.from("business_application")
|
||||||
) {
|
.update({
|
||||||
return client.from("business_application")
|
status: "reject",
|
||||||
.update({
|
})
|
||||||
status: "reject",
|
.eq("id", businessApplicationId);
|
||||||
})
|
|
||||||
.eq("id", businessApplicationId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function approveBusiness(
|
export async function approveBusiness(client: SupabaseClient, businessApplicationId: Number) {
|
||||||
client: SupabaseClient,
|
return client
|
||||||
businessApplicationId: Number,
|
.from("business_application")
|
||||||
) {
|
.update({
|
||||||
return client.from("business_application")
|
status: "approve",
|
||||||
.update({
|
})
|
||||||
status: "approve",
|
.eq("id", businessApplicationId);
|
||||||
})
|
}
|
||||||
.eq("id", businessApplicationId);
|
|
||||||
|
export async function rejectProject(client: SupabaseClient, projectApplicationId: Number) {
|
||||||
|
return client
|
||||||
|
.from("project_application")
|
||||||
|
.update({
|
||||||
|
status: "reject",
|
||||||
|
})
|
||||||
|
.eq("id", projectApplicationId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function approveProject(client: SupabaseClient, projectApplicationId: Number) {
|
||||||
|
return client
|
||||||
|
.from("project_application")
|
||||||
|
.update({
|
||||||
|
status: "approve",
|
||||||
|
})
|
||||||
|
.eq("id", projectApplicationId);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,3 +26,34 @@ export const getAllBusinessApplicationQuery = (client: SupabaseClient) => {
|
|||||||
`
|
`
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const getAllProjectApplicationByBusinessQuery = (client: SupabaseClient, businessId: number) => {
|
||||||
|
return client
|
||||||
|
.from("project_application")
|
||||||
|
.select(
|
||||||
|
`
|
||||||
|
id,
|
||||||
|
created_at,
|
||||||
|
deadline,
|
||||||
|
status,
|
||||||
|
project_name,
|
||||||
|
...business_id (
|
||||||
|
business_id:id,
|
||||||
|
business_name,
|
||||||
|
user_id
|
||||||
|
),
|
||||||
|
...project_type_id!inner (
|
||||||
|
project_type_id:id,
|
||||||
|
project_type_value:value
|
||||||
|
),
|
||||||
|
short_description,
|
||||||
|
pitch_deck_url,
|
||||||
|
project_logo,
|
||||||
|
min_investment,
|
||||||
|
target_investment,
|
||||||
|
user_id,
|
||||||
|
project_photos
|
||||||
|
`
|
||||||
|
)
|
||||||
|
.eq("business_id", businessId);
|
||||||
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user