feat: add admin page for project application

This commit is contained in:
sirin 2024-10-29 19:39:40 +07:00
parent 76a288f509
commit 1c58bbc8cf
4 changed files with 306 additions and 18 deletions

View 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>
);
}

View 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>
);
}

View File

@ -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);
}

View File

@ -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);
};