'use client';
import Link from 'next/link';
import { useState } from 'react';
import { useMutation } from '@tanstack/react-query';
import { toast } from 'sonner';
import { Icons } from '@/components/icons';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { Textarea } from '@/components/ui/textarea';
import {
approveApprovalMutation,
cancelApprovalMutation,
rejectApprovalMutation,
returnApprovalMutation
} from '../mutations';
import type { ApprovalDetailRecord } from '../types';
import { ApprovalStatusBadge } from './approval-status-badge';
function FieldItem({ label, value }: { label: string; value: string | null | undefined }) {
return (
);
}
export function ApprovalRequestPanel({
approval,
canApprove,
canReject,
canReturn,
canCancel,
activeBusinessRole,
isOrgAdmin,
currentUserId,
entityHref
}: {
approval: ApprovalDetailRecord;
canApprove: boolean;
canReject: boolean;
canReturn: boolean;
canCancel: boolean;
activeBusinessRole: string | null;
isOrgAdmin: boolean;
currentUserId: string;
entityHref?: string;
}) {
const [remark, setRemark] = useState('');
const isPending = approval.request.status === 'pending';
const canHandleCurrentStep =
!!approval.currentStep &&
(isOrgAdmin || activeBusinessRole === approval.currentStep.roleCode);
const canCancelCurrentRequest =
canCancel && isPending && (isOrgAdmin || approval.request.requestedBy === currentUserId);
const approveAction = useMutation({
...approveApprovalMutation,
onSuccess: () => {
toast.success('Approval step completed');
setRemark('');
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Unable to approve request')
});
const rejectAction = useMutation({
...rejectApprovalMutation,
onSuccess: () => {
toast.success('Approval request rejected');
setRemark('');
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Unable to reject request')
});
const returnAction = useMutation({
...returnApprovalMutation,
onSuccess: () => {
toast.success('Approval request returned to draft');
setRemark('');
},
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Unable to return request')
});
const cancelAction = useMutation({
...cancelApprovalMutation,
onSuccess: () => toast.success('Approval request cancelled'),
onError: (error) =>
toast.error(error instanceof Error ? error.message : 'Unable to cancel request')
});
const isActing =
approveAction.isPending ||
rejectAction.isPending ||
returnAction.isPending ||
cancelAction.isPending;
return (
Request Information
Current workflow state for this approval request.
{entityHref ? (
Open Related Document
) : null}
Workflow
Sequential approver chain configured for this document.
{approval.steps.map((step) => {
const isCurrent = approval.currentStep?.id === step.id && isPending;
const isCompleted = approval.request.currentStep > step.stepNumber || approval.request.status === 'approved';
return (
Step {step.stepNumber} - {step.roleName}
{step.roleCode}
{isCurrent ? 'Current' : isCompleted ? 'Completed' : 'Pending'}
);
})}
Actions
Approvers can approve, reject, or return on their assigned step.
Timeline
Full audit trail of request submission and approval actions.
{!approval.timeline.length ? (
No approval actions recorded yet.
) : (
approval.timeline.map((item, index) => (
{item.action}
{item.actorName ?? item.actedBy}
Step {item.stepNumber}
{new Date(item.actedAt).toLocaleString()}
{item.remark ?
{item.remark}
: null}
{index < approval.timeline.length - 1 ?
: null}
))
)}
);
}