koperasi/services/frontend/app/routes/app.my-projects.$id._index/components/second-section.tsx
2025-08-08 14:12:40 +07:00

182 lines
7.3 KiB
TypeScript

import { PDFDownloadLink } from "@react-pdf/renderer";
import { Link } from "@remix-run/react";
import { Check, Clock, Lock, X } from "lucide-react";
import { useEffect, useState } from "react";
import AgreementPDF, { type AgreementProps } from "~/components/document/aggrement-letter.client";
import { Button } from "~/components/ui/button";
import { useGetAgreementProject } from "~/services/projects/get-agreement-by-project-id";
import type { StatusData } from "~/types/constants/status-data";
import { formatImagePath } from "~/utils/prefix-file-path";
import SignContractDialog from "./modal/sign-contract";
type StatusType = "SUCCESS" | "FAILED" | "CURRENT" | "UPCOMING" | "PENDING";
export default function SecondSection({
id,
data,
handleClick,
}: { id: string; data: StatusData[]; handleClick: () => void }) {
const [contract, setContract] = useState<AgreementProps | null>(null);
const { data: contractData } = useGetAgreementProject(data[0].id || "");
const steps = [
"Proposal Project Terkirim",
"Peninjauan Proposal",
"Proses Approval dari Komitee Koperasi",
"Kontrak Perjanjian",
"Proses Penggalangan Penyertaan Modal",
];
const getLatestStatusForStep = (step: string) => {
const matchingStatuses = data.filter((status) => status.history === step);
return matchingStatuses.sort(
(a, b) => new Date(b.created_at).getTime() - new Date(a.created_at).getTime(),
)[0];
};
const getStepStatus = (index: number): StatusType => {
const step = steps[index];
const latestStatus = getLatestStatusForStep(step);
if (latestStatus) return latestStatus.status as unknown as StatusType;
const lastCompletedIndex = steps.findIndex((step) => !getLatestStatusForStep(step));
if (index === lastCompletedIndex) return "CURRENT";
return "UPCOMING";
};
const getStatusColor = (status: StatusType) => {
switch (status) {
case "SUCCESS":
return "bg-green-500 text-green-500";
case "FAILED":
return "bg-red-500 text-red-500";
case "CURRENT":
return "bg-blue-500 text-blue-500";
case "PENDING":
return "bg-yellow-400 text-yellow-500";
default:
return "bg-gray-300 text-gray-500";
}
};
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]);
return (
<section className="order-2 min-w-96 flex-1 space-y-6 rounded-md bg-white p-6 xl:order-1">
<h4 className="font-semibold text-lg">Progres Status Pengajuan Project</h4>
<div className="space-y-10">
{steps.map((step, index) => {
const stepData = getLatestStatusForStep(step);
const status = getStepStatus(index);
const statusColor = getStatusColor(status);
return (
<div key={index} className={`relative ${index < steps.length - 1 ? "h-32" : "h-fit"}`}>
<div className="flex items-start">
<div className="mr-4 flex flex-col items-center">
<div
className={`flex h-8 w-8 items-center justify-center rounded-full ${
// biome-ignore lint/nursery/useSortedClasses: <explanation>
statusColor.split(" ")[0]
}`}
>
{status === "SUCCESS" && <Check className="text-white" size={16} />}
{status === "FAILED" && <X className="text-white" size={16} />}
{status === "CURRENT" && (
<span className="font-bold text-white">{index + 1}</span>
)}
{status === "PENDING" && <Clock className="text-white" size={16} />}
{status === "UPCOMING" && <Lock className="text-white" size={16} />}
</div>
</div>
<div className="flex-1">
<h3
className={`font-semibold ${
// biome-ignore lint/nursery/useSortedClasses: <explanation>
statusColor.split(" ")[1]
}`}
>
{step}
</h3>
{stepData && (
<>
<p className="text-gray-500 text-sm">
{new Date(stepData.created_at).toLocaleString()}
</p>
<div
className={`mt-2 rounded p-3 text-sm ${
status === "SUCCESS"
? "bg-green-100 text-green-700"
: status === "FAILED"
? "bg-red-100 text-red-700"
: "bg-yellow-100 text-yellow-700"
}`}
// biome-ignore lint/security/noDangerouslySetInnerHtml: <explanation>
dangerouslySetInnerHTML={{
__html: stepData.keterangan,
}}
/>
</>
)}
{step === "Kontrak Perjanjian" ||
steps.indexOf(step) > steps.indexOf("Kontrak Perjanjian") ? (
contract ? (
<PDFDownloadLink
document={<AgreementPDF {...contract} />}
fileName="agreement.pdf"
>
<Button className="mt-2 w-full">Download Kontrak</Button>
</PDFDownloadLink>
) : (
<SignContractDialog id={id} />
)
) : null}
{status === "CURRENT" && step === "Peninjauan Proposal" ? (
<button
onClick={handleClick}
type="button"
className="mt-2 rounded border border-blue-500 bg-white px-4 py-2 text-blue-500 transition-colors hover:bg-blue-50"
>
Tinjau Proposal
</button>
) : status === "FAILED" ? (
<Button
asChild
type="button"
className="mt-2 rounded border border-red-500 bg-white px-4 py-2 text-red-500 transition-colors hover:bg-red-50"
>
<Link to={`/app/my-projects/${id}/edit`}>Ajukan Ulang</Link>
</Button>
) : (
<div className="mt-2 h-10" />
)}
</div>
</div>
{index < steps.length - 1 && (
<div className="absolute top-8 bottom-0 left-4 h-full w-0 border-gray-300 border-l-2 border-dashed" />
)}
</div>
);
})}
</div>
</section>
);
}