Re_Figma_Code/src/custom/components/request-detail/Form16QuickActions.tsx

206 lines
7.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Form 16 Quick Actions RE actions for the Quick Actions sidebar.
* Shown only for Form 16 requests when RE user and no credit note yet.
* Location: custom (Form 16 only); does not modify shared workflow components.
*/
import { useState, useEffect } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Loader2, Receipt, X, RotateCcw } from 'lucide-react';
import {
getCreditNoteByRequestId,
cancelForm16Submission,
setForm16ResubmissionNeeded,
generateForm16CreditNoteManually,
} from '@/services/form16Api';
import { toast } from 'sonner';
interface Form16QuickActionsProps {
requestId: string;
request: any;
onRefresh?: () => void;
}
export function Form16QuickActions({ requestId, request, onRefresh }: Form16QuickActionsProps) {
const [creditNote, setCreditNote] = useState<{ id: number; status: string } | null>(null);
const [loading, setLoading] = useState(true);
const [actionLoading, setActionLoading] = useState<'cancel' | 'resubmit' | 'credit' | null>(null);
const [generateCnOpen, setGenerateCnOpen] = useState(false);
const [generateCnAmount, setGenerateCnAmount] = useState('');
const form16 = request?.form16Submission;
const hasSubmission = !!form16;
const hasCreditNote = !!creditNote && creditNote.status !== 'withdrawn';
const suggestedAmount = form16?.tdsAmount != null ? Number(form16.tdsAmount) : undefined;
useEffect(() => {
if (!requestId) {
setLoading(false);
return;
}
let cancelled = false;
(async () => {
try {
const note = await getCreditNoteByRequestId(requestId);
if (!cancelled) setCreditNote(note ? { id: typeof note.id === 'number' ? note.id : Number(note.id), status: note.status || '' } : null);
} catch {
if (!cancelled) setCreditNote(null);
} finally {
if (!cancelled) setLoading(false);
}
})();
return () => { cancelled = true; };
}, [requestId]);
const handleCancelSubmission = async () => {
if (!requestId || !window.confirm('Cancel this Form 16 submission? The request will be marked as rejected.')) return;
setActionLoading('cancel');
try {
await cancelForm16Submission(requestId);
toast.success('Submission cancelled');
onRefresh?.();
} catch (e) {
toast.error(e instanceof Error ? e.message : 'Failed to cancel submission');
} finally {
setActionLoading(null);
}
};
const handleResubmissionNeeded = async () => {
if (!requestId || !window.confirm('Mark this submission as resubmission needed? The dealer will need to resubmit Form 16.')) return;
setActionLoading('resubmit');
try {
await setForm16ResubmissionNeeded(requestId);
toast.success('Marked as resubmission needed');
onRefresh?.();
} catch (e) {
toast.error(e instanceof Error ? e.message : 'Failed to update');
} finally {
setActionLoading(null);
}
};
const handleGenerateCreditNote = async () => {
const amount = parseFloat(generateCnAmount);
if (!requestId || Number.isNaN(amount) || amount <= 0) {
toast.error('Enter a valid amount to generate credit note');
return;
}
setActionLoading('credit');
try {
await generateForm16CreditNoteManually(requestId, amount);
setGenerateCnOpen(false);
setGenerateCnAmount('');
toast.success('Credit note generated (manually approved)');
const note = await getCreditNoteByRequestId(requestId);
setCreditNote(note ? { id: typeof note.id === 'number' ? note.id : Number(note.id), status: note.status || '' } : null);
onRefresh?.();
} catch (e) {
toast.error(e instanceof Error ? e.message : 'Failed to generate credit note');
} finally {
setActionLoading(null);
}
};
if (loading || !hasSubmission || hasCreditNote) return null;
return (
<>
<Card className="border-blue-200 bg-blue-50/30" data-testid="form16-quick-actions-card">
<CardHeader className="pb-2">
<CardTitle className="text-sm flex items-center gap-2 text-blue-800">
<Receipt className="w-4 h-4" />
Form 16 actions
</CardTitle>
<CardDescription className="text-xs text-gray-600">
View the document in the Documents tab. Cancel submission, mark resubmission needed, or generate credit note (e.g. when OCR was partial).
</CardDescription>
</CardHeader>
<CardContent className="space-y-2">
<Button
variant="outline"
size="sm"
className="w-full justify-start border-red-300 text-red-700 hover:bg-red-50"
onClick={handleCancelSubmission}
disabled={!!actionLoading}
>
{actionLoading === 'cancel' ? <Loader2 className="w-3 h-3 animate-spin mr-1" /> : <X className="w-3 h-3 mr-1" />}
Cancel submission
</Button>
<Button
variant="outline"
size="sm"
className="w-full justify-start border-amber-300 text-amber-700 hover:bg-amber-50"
onClick={handleResubmissionNeeded}
disabled={!!actionLoading}
>
{actionLoading === 'resubmit' ? <Loader2 className="w-3 h-3 animate-spin mr-1" /> : <RotateCcw className="w-3 h-3 mr-1" />}
Resubmission needed
</Button>
<Button
variant="outline"
size="sm"
className="w-full justify-start border-emerald-300 text-emerald-700 hover:bg-emerald-50"
onClick={() => {
setGenerateCnAmount(suggestedAmount != null ? String(suggestedAmount) : '');
setGenerateCnOpen(true);
}}
disabled={!!actionLoading}
>
{actionLoading === 'credit' ? <Loader2 className="w-3 h-3 animate-spin mr-1" /> : <Receipt className="w-3 h-3 mr-1" />}
Generate credit note
</Button>
</CardContent>
</Card>
<Dialog open={generateCnOpen} onOpenChange={setGenerateCnOpen}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Generate credit note (manual)</DialogTitle>
<DialogDescription>
Enter the amount for the credit note. This will mark the Form 16 as manually approved.
</DialogDescription>
</DialogHeader>
<div className="grid gap-4 py-4">
<div className="grid gap-2">
<Label htmlFor="form16-cn-amount">Amount ()</Label>
<Input
id="form16-cn-amount"
type="number"
min="0"
step="0.01"
placeholder={suggestedAmount != null ? String(suggestedAmount) : '0'}
value={generateCnAmount}
onChange={(e) => setGenerateCnAmount(e.target.value)}
/>
{suggestedAmount != null && (
<p className="text-xs text-gray-500">Suggested from submission TDS amount: {suggestedAmount.toLocaleString('en-IN')}</p>
)}
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={() => setGenerateCnOpen(false)} disabled={!!actionLoading}>
Cancel
</Button>
<Button onClick={handleGenerateCreditNote} disabled={!!actionLoading || !generateCnAmount.trim()}>
{actionLoading === 'credit' ? <Loader2 className="w-4 h-4 animate-spin mr-2" /> : null}
Generate credit note
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</>
);
}