"use client";
import { useState } from "react";
import { useRouter } from "next/navigation";
import type { ApplicationStage, InterviewOutcome, SalaryRateBasis } from "@prisma/client";
import { AdminDialog } from "@/components/ui/admin-dialog";
import {
advanceStage,
agreeSalary,
approveSalary,
returnSalary,
verifyDocuments,
recordReferenceCheck,
recordInterviewResult,
requestInterviewWaiver,
approveInterviewWaiver,
selectCandidate,
returnSelection,
rejectApplication,
onboardCandidate,
} from "./actions";
const INPUT =
"w-full rounded-lg border border-neutral-300 px-3 py-2 text-sm focus:border-primary-500 focus:outline-none focus:ring-2 focus:ring-primary-500/20";
const PRIMARY = "rounded-lg bg-primary-600 px-4 py-2 text-sm font-semibold text-white hover:bg-primary-700 disabled:opacity-60";
const SECONDARY = "rounded-lg border border-neutral-300 px-4 py-2 text-sm font-medium text-neutral-700 hover:bg-neutral-50 disabled:opacity-60";
const DANGER = "rounded-lg border border-danger-300 px-4 py-2 text-sm font-medium text-danger-700 hover:bg-danger-50 disabled:opacity-60";
export type ActionCardProps = {
id: string;
stage: ApplicationStage;
isExHand: boolean;
interviewResult: InterviewOutcome;
interviewWaived: boolean;
rejectedReason: string | null;
salaryPending: boolean;
waiverPending: boolean;
selectionPending: boolean;
employeeNo: string | null;
salary: { rateBasis: SalaryRateBasis; basic: number; victualingPerDay: number; currency: string; approved: boolean } | null;
perms: {
manage: boolean;
recordReference: boolean;
recordInterview: boolean;
requestWaiver: boolean;
approveSalary: boolean;
approveWaiver: boolean;
select: boolean;
onboard: boolean;
};
};
function useAction() {
const router = useRouter();
const [pending, setPending] = useState(false);
const [error, setError] = useState("");
async function run(fn: () => Promise<{ ok: true } | { error: string }>) {
setPending(true);
setError("");
const res = await fn();
setPending(false);
if ("error" in res) setError(res.error);
else router.refresh();
return res;
}
return { pending, error, run };
}
function Card({ title, sub, children }: { title: string; sub?: string; children: React.ReactNode }) {
return (
);
}
function RejectButton({ id }: { id: string }) {
const router = useRouter();
const [open, setOpen] = useState(false);
const [reason, setReason] = useState("");
const [pending, setPending] = useState(false);
const [error, setError] = useState("");
async function submit(e: React.FormEvent) {
e.preventDefault();
setPending(true); setError("");
const res = await rejectApplication(id, reason);
setPending(false);
if ("error" in res) setError(res.error); else { setOpen(false); router.refresh(); }
}
return (
<>
setOpen(false)}>
>
);
}
function Err({ msg }: { msg: string }) {
return msg ? {msg}
: null;
}
export function ApplicationActionCard(p: ActionCardProps) {
const { run, pending, error } = useAction();
const canReject = p.perms.manage && !["SELECTED", "ONBOARDED", "REJECTED"].includes(p.stage);
// Reference-check form state (COMPETENCY_AND_REFERENCES).
const [ref, setRef] = useState({ refereeName: "", refereeContact: "", outcome: "positive", note: "" });
// Bank/EPF form state (DOC_VERIFICATION).
const [docs, setDocs] = useState({ accountName: "", accountNumber: "", ifsc: "", bankName: "", uan: "", aadhaarLast4: "", pfNumber: "" });
// Salary form state (SALARY_AGREEMENT).
const [sal, setSal] = useState({ rateBasis: "MONTHLY", basic: "", victualingPerDay: "0", currency: "INR" });
function fdFrom(obj: Record, extra?: Record) {
const fd = new FormData();
Object.entries({ ...obj, ...extra }).forEach(([k, v]) => fd.set(k, v));
return fd;
}
const footer = (
<>
{canReject && (
)}
>
);
switch (p.stage) {
case "SHORTLISTED":
return (
{p.perms.manage && (
)}
{footer}
);
case "COMPETENCY_AND_REFERENCES":
return (
{p.perms.recordReference && (
)}
{p.perms.manage && (
)}
{footer}
);
case "DOC_VERIFICATION":
return (
{p.perms.manage ? (
<>
setDocs({ ...docs, accountName: e.target.value })} />
setDocs({ ...docs, accountNumber: e.target.value })} />
setDocs({ ...docs, ifsc: e.target.value })} />
setDocs({ ...docs, bankName: e.target.value })} />
setDocs({ ...docs, uan: e.target.value })} />
setDocs({ ...docs, aadhaarLast4: e.target.value })} />
setDocs({ ...docs, pfNumber: e.target.value })} />
>
) : (
Awaiting document verification by the MPO.
)}
{footer}
);
case "SALARY_AGREEMENT":
if (p.salaryPending) {
return (
Proposed: {p.salary?.currency} {p.salary?.basic} / {p.salary?.rateBasis.toLowerCase()} · victualing {p.salary?.currency} {p.salary?.victualingPerDay}/day
{p.perms.approveSalary ? (
returnSalary(p.id, reason)} />
) : (
Awaiting Manager approval.
)}
{footer}
);
}
return (
{p.perms.manage ? (
<>
setSal({ ...sal, basic: e.target.value })} />
setSal({ ...sal, victualingPerDay: e.target.value })} />
>
) : (
Awaiting the MPO to agree the salary.
)}
{footer}
);
case "PROPOSED":
return (
{p.perms.manage && (
)}
{footer}
);
case "INTERVIEW":
return (
{/* Interview result row */}
{p.interviewResult === "PENDING" && !p.interviewWaived && p.perms.recordInterview && (
)}
{/* Waiver (ex-hand) */}
{p.isExHand && !p.interviewWaived && p.interviewResult === "PENDING" && !p.waiverPending && p.perms.requestWaiver && (
)}
{p.waiverPending && (
p.perms.approveWaiver ? (
Waiver requested.
) : (
Interview waiver awaiting Manager approval.
)
)}
{/* Selection row */}
{(p.interviewResult === "ACCEPTED" || p.interviewWaived) && (
p.perms.select ? (
returnSelection(p.id, reason)} />
) : (
{p.interviewWaived ? "Interview waived" : "Interview passed"} — awaiting Manager selection.
)
)}
{footer}
);
case "SELECTED":
return (
Candidate selected.
{p.perms.onboard && }
);
case "REJECTED":
return (
{p.rejectedReason ?? "This candidate was rejected."}
);
default:
return (
Onboarded to crew{p.employeeNo ? <> · {p.employeeNo}> : null}.
);
}
}
function OnboardButton({ id }: { id: string }) {
const router = useRouter();
const [open, setOpen] = useState(false);
const [joiningDate, setJoiningDate] = useState("");
const [contract, setContract] = useState(null);
const [pending, setPending] = useState(false);
const [error, setError] = useState("");
async function submit(e: React.FormEvent) {
e.preventDefault();
setPending(true); setError("");
const fd = new FormData();
fd.set("applicationId", id);
fd.set("joiningDate", joiningDate);
if (contract) fd.set("contract", contract);
const res = await onboardCandidate(fd);
setPending(false);
if ("error" in res) setError(res.error); else { setOpen(false); router.refresh(); }
}
return (
<>
setOpen(false)}>
>
);
}
function ReturnButton({ label, onReturn }: { label: string; onReturn: (reason: string) => Promise<{ ok: true } | { error: string }> }) {
const router = useRouter();
const [open, setOpen] = useState(false);
const [reason, setReason] = useState("");
const [pending, setPending] = useState(false);
const [error, setError] = useState("");
async function submit(e: React.FormEvent) {
e.preventDefault();
setPending(true); setError("");
const res = await onReturn(reason);
setPending(false);
if ("error" in res) setError(res.error); else { setOpen(false); router.refresh(); }
}
return (
<>
setOpen(false)}>
>
);
}