126 lines
5.1 KiB
TypeScript
126 lines
5.1 KiB
TypeScript
/**
|
||
* Form 16 Details – Workflow tab.
|
||
* Shows the Form 16 process: Dealer submits → OCR extracts → 26AS match → Credit note.
|
||
* RE actions (cancel, resubmission needed, generate credit note) are in the Quick Actions sidebar.
|
||
*/
|
||
|
||
import { useState, useEffect } from 'react';
|
||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||
import { CheckCircle, Circle, XCircle, FileText, RefreshCw, Loader2, Receipt } from 'lucide-react';
|
||
import { getCreditNoteByRequestId, type Form16CreditNoteItem } from '@/services/form16Api';
|
||
|
||
interface Form16WorkflowTabProps {
|
||
request: any;
|
||
requestId: string;
|
||
isReUser: boolean;
|
||
onRefresh?: () => void;
|
||
}
|
||
|
||
const steps = [
|
||
{ key: 'submit', label: 'Dealer submits Form 16', icon: FileText },
|
||
{ key: 'ocr', label: 'OCR extracts', icon: FileText },
|
||
{ key: 'match', label: 'Matching with 26AS', icon: RefreshCw },
|
||
{ key: 'success', label: 'Successfully match', icon: CheckCircle },
|
||
{ key: 'credit', label: 'Credit note generated', icon: Receipt },
|
||
];
|
||
|
||
export function Form16WorkflowTab({ request, requestId }: Form16WorkflowTabProps) {
|
||
const [creditNote, setCreditNote] = useState<Form16CreditNoteItem | null>(null);
|
||
const [loading, setLoading] = useState(true);
|
||
|
||
const form16 = request?.form16Submission;
|
||
const hasSubmission = !!form16;
|
||
const hasCreditNote = !!creditNote && creditNote.status !== 'withdrawn';
|
||
const creditNoteWithdrawn = !!creditNote && creditNote.status === 'withdrawn';
|
||
const validationStatus = form16?.validationStatus ?? null;
|
||
const validationFailed = validationStatus === 'failed' || validationStatus === 'mismatch' || validationStatus === 'duplicate';
|
||
const validationSuccess = hasCreditNote || validationStatus === 'success' || validationStatus === 'manually_approved';
|
||
|
||
useEffect(() => {
|
||
if (!requestId) {
|
||
setLoading(false);
|
||
return;
|
||
}
|
||
let cancelled = false;
|
||
(async () => {
|
||
try {
|
||
const note = await getCreditNoteByRequestId(requestId);
|
||
if (!cancelled) setCreditNote(note);
|
||
} catch {
|
||
if (!cancelled) setCreditNote(null);
|
||
} finally {
|
||
if (!cancelled) setLoading(false);
|
||
}
|
||
})();
|
||
return () => { cancelled = true; };
|
||
}, [requestId]);
|
||
|
||
const stepStatus: Record<string, boolean> = {
|
||
submit: true,
|
||
ocr: hasSubmission,
|
||
match: validationSuccess,
|
||
success: validationSuccess,
|
||
credit: hasCreditNote || creditNoteWithdrawn,
|
||
};
|
||
const stepFailed: Record<string, boolean> = {
|
||
submit: false,
|
||
ocr: false,
|
||
match: validationFailed,
|
||
success: validationFailed,
|
||
credit: false,
|
||
};
|
||
|
||
return (
|
||
<div className="space-y-6" data-testid="form16-workflow-tab">
|
||
<Card className="border-emerald-200">
|
||
<CardHeader className="pb-2">
|
||
<CardTitle className="text-base flex items-center gap-2 text-emerald-800">
|
||
<RefreshCw className="w-4 h-4" />
|
||
Form 16 process
|
||
</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
{steps.map((step) => {
|
||
const done = stepStatus[step.key];
|
||
const failed = stepFailed[step.key];
|
||
return (
|
||
<div key={step.key} className="flex items-start gap-3">
|
||
<div className={`flex-shrink-0 w-8 h-8 rounded-full flex items-center justify-center ${failed ? 'bg-red-100 text-red-600' : done ? 'bg-emerald-100 text-emerald-700' : 'bg-gray-100 text-gray-400'}`}>
|
||
{failed ? <XCircle className="w-4 h-4" /> : done ? <CheckCircle className="w-4 h-4" /> : <Circle className="w-4 h-4" />}
|
||
</div>
|
||
<div className="flex-1 min-w-0">
|
||
<p className={`text-sm font-medium ${failed ? 'text-red-700' : done ? 'text-gray-900' : 'text-gray-500'}`}>{step.label}</p>
|
||
{step.key === 'credit' && hasCreditNote && creditNote && (
|
||
<p className="text-xs text-gray-500 mt-0.5">
|
||
Credit note: {creditNote.creditNoteNumber}
|
||
{creditNote.amount != null && ` · ${new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR', maximumFractionDigits: 0 }).format(Number(creditNote.amount))}`}
|
||
{validationStatus === 'manually_approved' && (
|
||
<span className="ml-1 text-blue-600">(Manually approved Form 16)</span>
|
||
)}
|
||
</p>
|
||
)}
|
||
{step.key === 'credit' && creditNoteWithdrawn && (
|
||
<p className="text-xs text-amber-600 mt-0.5">Withdrawn</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
);
|
||
})}
|
||
</CardContent>
|
||
</Card>
|
||
|
||
{loading && (
|
||
<div className="flex items-center gap-2 text-sm text-gray-500">
|
||
<Loader2 className="w-4 h-4 animate-spin" />
|
||
Loading credit note…
|
||
</div>
|
||
)}
|
||
|
||
{!hasCreditNote && !loading && hasSubmission && (
|
||
<p className="text-xs text-gray-500">No credit note has been generated for this submission yet. Use Quick Actions (sidebar) for RE actions.</p>
|
||
)}
|
||
</div>
|
||
);
|
||
}
|
||
|