import { useEffect, useMemo, useState } from 'react'; import { Card, CardContent, CardHeader, CardTitle } from '../ui/card'; import { Button } from '../ui/button'; import { Input } from '../ui/input'; import { Label } from '../ui/label'; import { Select, SelectContent, SelectGroup, SelectItem, SelectLabel, SelectTrigger, SelectValue } from '../ui/select'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../ui/table'; import { Badge } from '../ui/badge'; import { RefreshCw, Settings2, Edit2, Save, X, Plus } from 'lucide-react'; import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, DialogDescription } from '../ui/dialog'; import { DropdownMenu, DropdownMenuCheckboxItem, DropdownMenuContent, DropdownMenuLabel, DropdownMenuSeparator, DropdownMenuTrigger } from '../ui/dropdown-menu'; import { ROLES } from '../../lib/constants'; import { approvalPolicyService } from '../../services/approvalPolicy.service'; type ApprovalMode = 'ALL' | 'MIN_N' | 'ROLE_MANDATORY'; interface Policy { stageCode: string; minApprovals: number; approvalMode: ApprovalMode; requiredRoles: string[]; isActive: boolean; } const AVAILABLE_ROLES = Object.values(ROLES).sort(); const STAGE_OPTIONS = [ { label: 'Onboarding', stages: [ { label: 'General Info', value: 'ONBOARDING_GENERAL' }, { label: 'KYC Verification', value: 'ONBOARDING_KYC' }, { label: 'Level 1 Interview', value: 'LEVEL_1_INTERVIEW' }, { label: 'Level 2 Interview', value: 'LEVEL_2_INTERVIEW' }, { label: 'Level 3 Interview', value: 'LEVEL_3_INTERVIEW' }, { label: 'FDD Verification', value: 'FDD_VERIFICATION' }, { label: 'LOI Approval', value: 'LOI_APPROVAL' }, { label: 'LOA Approval', value: 'LOA_APPROVAL' }, { label: 'Architecture Team Assigned', value: 'ARCHITECTURE_ASSIGNMENT' }, { label: 'Architecture Doc Upload', value: 'ARCHITECTURE_DOCUMENT_UPLOAD' }, { label: 'Statutory Verification', value: 'STATUTORY_CHECK' }, { label: 'EOR Verification', value: 'EOR_VERIFICATION' }, ] }, { label: 'Offboarding (Resignation)', stages: [ { label: 'Regional Review', value: 'RESIGNATION_REGIONAL_REVIEW' }, { label: 'ZM Review', value: 'RESIGNATION_ZM_REVIEW' }, { label: 'ZBH Review', value: 'RESIGNATION_ZBH_REVIEW' }, { label: 'Finance Clearance', value: 'RESIGNATION_FINANCE_REVIEW' }, { label: 'DDL Review', value: 'RESIGNATION_DDL_REVIEW' }, { label: 'Final Approval', value: 'RESIGNATION_APPROVED' }, ] }, { label: 'Termination', stages: [ { label: 'RBM Review', value: 'TERMINATION_HEARING' }, { label: 'DDL Evaluation', value: 'TERMINATION_REVIEW' }, { label: 'Legal Verification', value: 'TERMINATION_LEGAL_VERIFICATION' }, { label: 'Final NBH Approval', value: 'TERMINATION_CLOSED' }, ] }, { label: 'Relocation & CC', stages: [ { label: 'Relocation ASM Review', value: 'RELOCATION_ASM_REVIEW' }, { label: 'Relocation Head Approval', value: 'RELOCATION_COMPLETED' }, { label: 'CC Legal Review', value: 'CONSTITUTIONAL_LEGAL_REVIEW' }, { label: 'CC Head Approval', value: 'CONSTITUTIONAL_APPROVED' }, ] } ]; export function ApprovalPoliciesPage() { const [loading, setLoading] = useState(false); const [policies, setPolicies] = useState([]); const [isModalOpen, setIsModalOpen] = useState(false); const [isEditMode, setIsEditMode] = useState(false); const [isCustomStage, setIsCustomStage] = useState(false); const [draft, setDraft] = useState({ stageCode: '', minApprovals: 1, approvalMode: 'MIN_N', requiredRoles: [], isActive: true }); const sortedPolicies = useMemo( () => [...policies].sort((a, b) => a.stageCode.localeCompare(b.stageCode)), [policies] ); const fetchPolicies = async () => { setLoading(true); try { const res = await approvalPolicyService.getPolicies(); if (res?.success) setPolicies(res.data || []); } finally { setLoading(false); } }; useEffect(() => { fetchPolicies(); }, []); const openCreateModal = () => { setIsEditMode(false); setIsCustomStage(false); setDraft({ stageCode: '', minApprovals: 1, approvalMode: 'MIN_N', requiredRoles: [], isActive: true }); setIsModalOpen(true); }; const openEditModal = (policy: Policy) => { setIsEditMode(true); setIsCustomStage(true); // Always treat existings as "custom entry" if not found in list, for UI consistency setDraft({ stageCode: policy.stageCode, minApprovals: policy.minApprovals || 1, approvalMode: (policy.approvalMode as ApprovalMode) || 'MIN_N', requiredRoles: Array.isArray(policy.requiredRoles) ? [...policy.requiredRoles] : [], isActive: policy.isActive !== false }); setIsModalOpen(true); }; const savePolicy = async () => { if (!draft.stageCode.trim()) return; if (draft.approvalMode === 'ROLE_MANDATORY' && draft.requiredRoles.length > 0 && draft.minApprovals > draft.requiredRoles.length) { alert('In ROLE_MANDATORY mode, min approvals cannot exceed the number of required roles.'); return; } const payload = { minApprovals: Number(draft.minApprovals) || 1, approvalMode: draft.approvalMode, requiredRoles: draft.requiredRoles, isActive: draft.isActive }; const stageCode = draft.stageCode.trim().toUpperCase().replace(/\s+/g, '_'); const res = await approvalPolicyService.savePolicy(stageCode, payload); if (res?.success) { await fetchPolicies(); setIsModalOpen(false); } }; return (

Approval Policies

Configure stage-level approvers, mode, and minimum approvals.

Configured Stages
Stage Code Approval Mode Min Appr. Required Roles Status Actions {sortedPolicies.map((policy) => ( {policy.stageCode} {policy.approvalMode} {policy.minApprovals}
{(policy.requiredRoles || []).map((role) => ( {role} ))}
{policy.isActive ? 'Active' : 'Inactive'}
))}
{/* Unified Edit/Create Modal */} {isEditMode ? : } {isEditMode ? 'Edit Policy' : 'Create New Policy'} {isEditMode ? `Update configuration for stage ${draft.stageCode}.` : 'Define approval requirements for a workflow stage.'}
{!isCustomStage && !isEditMode ? (
) : (
setDraft({ ...draft, stageCode: e.target.value.toUpperCase() })} /> {!isEditMode && ( { setIsCustomStage(false); setDraft({ ...draft, stageCode: '' }); }} /> )}
)}
setDraft({ ...draft, minApprovals: Number(e.target.value || 1) })} className="w-20 h-8 text-xs border-slate-200" />
Available Roles {AVAILABLE_ROLES.map((role) => ( { if (checked) { setDraft({ ...draft, requiredRoles: [...draft.requiredRoles, role] }); } else { setDraft({ ...draft, requiredRoles: draft.requiredRoles.filter(r => r !== role) }); } }} > {role} ))}
{draft.requiredRoles.map((role) => ( {role} setDraft({ ...draft, requiredRoles: draft.requiredRoles.filter(r => r !== role) })} /> ))} {draft.requiredRoles.length === 0 && ( No roles assigned. )}
); }