267 lines
11 KiB
TypeScript
267 lines
11 KiB
TypeScript
import { PDFDownloadLink } from "@react-pdf/renderer";
|
|
import type { LoaderFunctionArgs } from "@remix-run/node";
|
|
import { useLoaderData } from "@remix-run/react";
|
|
import { User2 } from "lucide-react";
|
|
import { useEffect, useState } from "react";
|
|
import { toast } from "sonner";
|
|
import BackButton from "~/components/back-button";
|
|
import AgreementPDF, { type AgreementProps } from "~/components/document/aggrement-letter.client";
|
|
import PageContainer from "~/components/page-container";
|
|
import { Badge } from "~/components/ui/badge";
|
|
import { Button } from "~/components/ui/button";
|
|
import { Separator } from "~/components/ui/separator";
|
|
import { httpClient } from "~/lib/http";
|
|
import { useGetHistoryProjectByID } from "~/services/history-project/get-by-id";
|
|
import { useAcceptProject } from "~/services/projects/accept";
|
|
import { useComplateProject } from "~/services/projects/completing";
|
|
import { useGetAgreementProject } from "~/services/projects/get-agreement-by-project-id";
|
|
import type { Proyek } from "~/types/api/proyek";
|
|
import type { BaseResponse } from "~/types/constants/base-response";
|
|
import { StatusProject } from "~/types/constants/status-project";
|
|
import { formatImagePath } from "~/utils/prefix-file-path";
|
|
import toRupiah from "~/utils/to-rupiah";
|
|
import FirstSection from "./components/first-section";
|
|
import ApproveModal from "./components/modal/approval";
|
|
import PublishModal from "./components/modal/publish";
|
|
import RevisiModal from "./components/modal/revisi";
|
|
import { SelesaiModal } from "./components/modal/selesai";
|
|
import { TerimaModal } from "./components/modal/terima";
|
|
import TolakModal from "./components/modal/tolak";
|
|
import SecondSection from "./components/second-section";
|
|
|
|
export async function loader({ params }: LoaderFunctionArgs) {
|
|
const response = await httpClient.get<BaseResponse<Proyek>>(`/project/${params.id}`);
|
|
if (response.data.data === undefined) return {} as Proyek;
|
|
return response.data.data;
|
|
}
|
|
|
|
export default function DetailPengajuanProyekPage() {
|
|
const data = useLoaderData<typeof loader>();
|
|
const [contract, setContract] = useState<AgreementProps | null>(null);
|
|
|
|
const [phase, setPhase] = useState(1);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const { data: contractData } = useGetAgreementProject(data.id || "");
|
|
const { mutateAsync: acceptProject } = useAcceptProject(data.id || "");
|
|
const { mutateAsync: complateProject } = useComplateProject(data.id || "");
|
|
const { data: statusData } = useGetHistoryProjectByID(data.id || "");
|
|
|
|
const isRunningCycle = (status: StatusProject | undefined): boolean => {
|
|
if (!status) return false;
|
|
return status === StatusProject.BERJALAN || status.toString().startsWith("BERJALAN SIKLUS");
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (contractData) {
|
|
setContract({
|
|
idProjek: contractData.idProjek,
|
|
idUser: contractData.idUser,
|
|
namaProyek: contractData.namaProyek,
|
|
namaPetugas: contractData.namaPetugas,
|
|
alamatPetugas: contractData.alamatPetugas,
|
|
namaPemilikProyek: contractData.namaPemilikProyek,
|
|
nik: contractData.nik,
|
|
noHp: contractData.noHp,
|
|
alamat: contractData.alamat,
|
|
tandaTangan: formatImagePath(contractData.tandaTangan),
|
|
signature: formatImagePath(contractData.signature),
|
|
nominalDisetujui: contractData.nominalDisetujui,
|
|
tanggal: contractData.createdAt,
|
|
});
|
|
}
|
|
}, [contractData]);
|
|
|
|
function handleChangePhase() {
|
|
setPhase((prev) => (prev === 1 ? 2 : 1));
|
|
}
|
|
|
|
async function handleAccept() {
|
|
try {
|
|
setIsLoading(true);
|
|
await acceptProject();
|
|
toast.success("Proyek berhasil diterima");
|
|
} catch (_) {
|
|
toast.error("Terjadi kesalahan saat menerima proyek");
|
|
}
|
|
setIsLoading(false);
|
|
}
|
|
|
|
async function handleComplate() {
|
|
try {
|
|
setIsLoading(true);
|
|
await complateProject();
|
|
toast.success("Proyek berhasil diselesaikan");
|
|
} catch (_) {
|
|
toast.error("Terjadi kesalahan saat menyelesaikan proyek");
|
|
}
|
|
setIsLoading(false);
|
|
}
|
|
|
|
return (
|
|
<PageContainer>
|
|
<div className="space-y-8">
|
|
<div className="flex items-center gap-3">
|
|
<BackButton to="/dashboard/proyek/pengajuan" />
|
|
<h2 className="~text-base/2xl font-bold tracking-tight">Detail Proyek</h2>
|
|
</div>
|
|
<div className="flex">
|
|
<button
|
|
type="button"
|
|
className="group flex cursor-pointer flex-col gap-2"
|
|
onClick={handleChangePhase}
|
|
>
|
|
<span
|
|
className={`px-5 font-medium ${
|
|
phase === 1 ? "text-primary" : "text-gray-600 group-hover:text-primary"
|
|
}`}
|
|
>
|
|
Informasi Proyek
|
|
</span>
|
|
<Separator className={`${phase === 1 ? "h-[2px] bg-primary" : "hidden"}`} />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className="group flex cursor-pointer flex-col gap-2"
|
|
onClick={handleChangePhase}
|
|
>
|
|
<span
|
|
className={`px-5 font-medium ${
|
|
phase === 2 ? "text-primary" : "text-gray-600 group-hover:text-primary"
|
|
}`}
|
|
>
|
|
Status Pengajuan Proyek
|
|
</span>
|
|
<Separator className={`${phase === 2 ? "h-[2px] bg-primary" : "hidden"}`} />
|
|
</button>
|
|
</div>
|
|
<div className="flex flex-col gap-6 xl:flex-row">
|
|
<div className="h-fit flex-1">
|
|
{phase === 1 ? (
|
|
<FirstSection
|
|
report_progress={data.report_progress || ""}
|
|
brosur={data.brosur_produk?.toString() || ""}
|
|
deskripsi={data.deskripsi}
|
|
dokumenProyeksi={data.dokumen_proyeksi?.toString() || ""}
|
|
dokumenTambahan={data.dokumenTambahan || []}
|
|
bagian_pelaksana={data.bagian_pelaksana || ""}
|
|
bagian_koperasi={data.bagian_koperasi || ""}
|
|
bagian_pemilik={data.bagian_pemilik || ""}
|
|
bagian_pendana={data.bagian_pendana || ""}
|
|
/>
|
|
) : (
|
|
<SecondSection data={statusData || []} handleClick={handleChangePhase} />
|
|
)}
|
|
</div>
|
|
<section className="order-1 min-w-96 max-w-full space-y-3 xl:order-2">
|
|
<div className="space-y-6 rounded-md bg-white p-6">
|
|
<div className="flex items-center justify-between">
|
|
<div className="rounded-full bg-gray-200 p-2">
|
|
<User2 size={24} className="text-primary" />
|
|
</div>
|
|
<Badge
|
|
className={`text-white uppercase ${
|
|
data.status === StatusProject.APPROVAL ||
|
|
data.status === StatusProject.PROSES_VERIFIKASI
|
|
? "bg-green-500"
|
|
: data.status === StatusProject.BERJALAN ||
|
|
data.status === StatusProject.PENDANAAN_DIBUKA ||
|
|
data.status === StatusProject.TTD_KONTRAK ||
|
|
data.status === StatusProject.SELESAI
|
|
? "bg-blue-500"
|
|
: "bg-red-500"
|
|
}`}
|
|
>
|
|
{data.status}
|
|
</Badge>
|
|
</div>
|
|
<div className="space-y-1">
|
|
<h4 className="font-semibold text-lg uppercase">{data.judul}</h4>
|
|
<h4 className="font-semibold text-gray-500 uppercase">{data.user?.nama}</h4>
|
|
</div>
|
|
<div className="space-y-4">
|
|
<div className="space-y-2">
|
|
<div className="flex items-center justify-between">
|
|
<span className="flex-1 text-gray-500">Target Dana</span>
|
|
<span className="w-fit">{toRupiah(data.nominal)}</span>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="flex-1 text-gray-500">Aset Jaminan</span>
|
|
<span className="w-fit uppercase">{data.asset_jaminan}</span>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="flex-1 text-gray-500">Nominal Jaminan</span>
|
|
<span className="w-fit">{toRupiah(data.nilai_jaminan)}</span>
|
|
</div>
|
|
</div>
|
|
<Separator />
|
|
<div className="space-y-2">
|
|
<h4 className="font-medium">Model dan Rencana Bisnis</h4>
|
|
<div className="flex items-center justify-between">
|
|
<span className="flex-1 text-gray-500">
|
|
Pendapatan/{data.report_progress} bulan
|
|
</span>
|
|
<span className="w-fit">{toRupiah(data.pendapatan_perbulan)}</span>
|
|
</div>
|
|
<div className="flex items-center justify-between">
|
|
<span className="flex-1 text-gray-500">
|
|
Pengeluaran/{data.report_progress} bulan
|
|
</span>
|
|
<span className="w-fit">{toRupiah(data.pengeluaran_perbulan)}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2 rounded-md bg-white p-6">
|
|
<h4 className="font-semibold text-lg">Kontak Pemilik Proyek</h4>
|
|
<div className="flex items-center justify-between">
|
|
<span className="flex-1 text-gray-500">No.Whatsapp</span>
|
|
<span className="w-fit text-primary">{data.user?.no_hp}</span>
|
|
</div>
|
|
</div>
|
|
<div className="space-y-2 rounded-md bg-white p-6">
|
|
<h4 className="font-semibold text-lg">Aksi</h4>
|
|
{data.status === StatusProject.TTD_KONTRAK && contract && (
|
|
<PDFDownloadLink document={<AgreementPDF {...contract} />} fileName="agreement.pdf">
|
|
<Button className="mt-2 w-full">Download Kontrak</Button>
|
|
</PDFDownloadLink>
|
|
)}
|
|
{data.status !== StatusProject.PENDANAAN_DIBUKA ? (
|
|
<RevisiModal id={data.id || ""} />
|
|
) : (
|
|
<>
|
|
<Button className="w-full bg-blue-700 hover:bg-blue-600">Projek Selesai</Button>
|
|
</>
|
|
)}
|
|
<div className="grid grid-cols-2 gap-2">
|
|
{data.status === StatusProject.APPROVAL && <ApproveModal data={data as Proyek} />}
|
|
{data.status === StatusProject.TTD_KONTRAK && (
|
|
<PublishModal id={data.id || ""} data={data as Proyek} />
|
|
)}
|
|
{data.status !== StatusProject.PENDANAAN_DIBUKA && (
|
|
<>
|
|
<TerimaModal loading={isLoading} onSubmitted={handleAccept} />
|
|
<TolakModal id={data.id || ""} />
|
|
</>
|
|
)}
|
|
{isRunningCycle(data.status as StatusProject) && (
|
|
<>
|
|
<SelesaiModal loading={isLoading} onSubmitted={handleComplate} />
|
|
{contract && (
|
|
<PDFDownloadLink
|
|
document={<AgreementPDF {...contract} />}
|
|
fileName="agreement.pdf"
|
|
>
|
|
<Button className="mt-2 w-full">Download Kontrak</Button>
|
|
</PDFDownloadLink>
|
|
)}
|
|
</>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</PageContainer>
|
|
);
|
|
}
|