mirror of
https://github.com/Sosokker/B2D-Ventures.git
synced 2025-12-18 21:44: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";
|
||||
|
||||
export async function rejectBusiness(
|
||||
client: SupabaseClient,
|
||||
businessApplicationId: Number,
|
||||
) {
|
||||
return client.from("business_application")
|
||||
.update({
|
||||
status: "reject",
|
||||
})
|
||||
.eq("id", businessApplicationId);
|
||||
export async function rejectBusiness(client: SupabaseClient, businessApplicationId: Number) {
|
||||
return client
|
||||
.from("business_application")
|
||||
.update({
|
||||
status: "reject",
|
||||
})
|
||||
.eq("id", businessApplicationId);
|
||||
}
|
||||
|
||||
export async function approveBusiness(
|
||||
client: SupabaseClient,
|
||||
businessApplicationId: Number,
|
||||
) {
|
||||
return client.from("business_application")
|
||||
.update({
|
||||
status: "approve",
|
||||
})
|
||||
.eq("id", businessApplicationId);
|
||||
export async function approveBusiness(client: SupabaseClient, businessApplicationId: Number) {
|
||||
return client
|
||||
.from("business_application")
|
||||
.update({
|
||||
status: "approve",
|
||||
})
|
||||
.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