diff --git a/src/dealer-claim/components/request-detail/OverviewTab.tsx b/src/dealer-claim/components/request-detail/OverviewTab.tsx
index 485a96a..e355b7d 100644
--- a/src/dealer-claim/components/request-detail/OverviewTab.tsx
+++ b/src/dealer-claim/components/request-detail/OverviewTab.tsx
@@ -18,6 +18,12 @@ import {
getRoleBasedVisibility,
type RequestRole,
} from '@/utils/claimDataMapper';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
+import { Button } from '@/components/ui/button';
+import { RichTextEditor } from '@/components/ui/rich-text-editor';
+import { FormattedDescription } from '@/components/common/FormattedDescription';
+import { CheckCircle, RefreshCw, Loader2 } from 'lucide-react';
+import { formatDateTime } from '@/utils/dateFormatter';
interface ClaimManagementOverviewTabProps {
request: any; // Original request object
@@ -26,6 +32,15 @@ interface ClaimManagementOverviewTabProps {
isInitiator: boolean;
onEditClaimAmount?: () => void;
className?: string;
+ // Closure props
+ needsClosure?: boolean;
+ conclusionRemark?: string;
+ setConclusionRemark?: (value: string) => void;
+ conclusionLoading?: boolean;
+ conclusionSubmitting?: boolean;
+ aiGenerated?: boolean;
+ handleGenerateConclusion?: () => void;
+ handleFinalizeConclusion?: () => void;
}
export function ClaimManagementOverviewTab({
@@ -35,6 +50,14 @@ export function ClaimManagementOverviewTab({
isInitiator: _isInitiator,
onEditClaimAmount: _onEditClaimAmount,
className = '',
+ needsClosure = false,
+ conclusionRemark = '',
+ setConclusionRemark,
+ conclusionLoading = false,
+ conclusionSubmitting = false,
+ aiGenerated = false,
+ handleGenerateConclusion,
+ handleFinalizeConclusion,
}: ClaimManagementOverviewTabProps) {
// Check if this is a claim management request
if (!isClaimManagementRequest(apiRequest)) {
@@ -98,6 +121,21 @@ export function ClaimManagementOverviewTab({
phone: apiRequest.initiator?.phone || apiRequest.initiator?.mobile,
};
+ // Debug: Log closure props to help troubleshoot
+ console.debug('[ClaimManagementOverviewTab] Closure setup check:', {
+ needsClosure,
+ requestStatus: apiRequest?.status,
+ requestStatusLower: (apiRequest?.status || '').toLowerCase(),
+ hasConclusionRemark: !!conclusionRemark,
+ conclusionRemarkLength: conclusionRemark?.length || 0,
+ conclusionLoading,
+ conclusionSubmitting,
+ aiGenerated,
+ hasHandleGenerate: !!handleGenerateConclusion,
+ hasHandleFinalize: !!handleFinalizeConclusion,
+ hasSetConclusion: !!setConclusionRemark,
+ });
+
return (
{/* Activity Information - Always visible */}
@@ -118,6 +156,153 @@ export function ClaimManagementOverviewTab({
{/* Request Initiator */}
+
+ {/* Closed Request Conclusion Remark Display */}
+ {apiRequest?.status === 'closed' && apiRequest?.conclusionRemark && (
+
+
+
+
+ Conclusion Remark
+
+ Final summary of this closed request
+
+
+
+
+
+
+ {apiRequest.closureDate && (
+
+ Request closed on {formatDateTime(apiRequest.closureDate)}
+ By {initiatorInfo.name}
+
+ )}
+
+
+ )}
+
+ {/* Conclusion Remark Section - Closure Setup */}
+ {needsClosure && (
+
+
+
+
+
+
+ Conclusion Remark - Final Step
+
+
+ {(apiRequest?.status || '').toLowerCase() === 'rejected'
+ ? 'This request was rejected. Please review the AI-generated closure remark and finalize it to close this request.'
+ : 'All approvals are complete. Please review and finalize the conclusion to close this request.'}
+
+
+ {handleGenerateConclusion && (
+
+
+ {aiGenerated ? 'Regenerate' : 'Generate with AI'}
+
+ )}
+
+
+
+ {conclusionLoading ? (
+
+
+
+
Preparing conclusion remark...
+
+
+ ) : (
+
+
+
+ Conclusion Remark
+ {aiGenerated && (
+
+ ✓ System-generated suggestion (editable)
+
+ )}
+
+
+ {setConclusionRemark && (
+
setConclusionRemark(html)}
+ placeholder="Enter a professional conclusion remark summarizing the request outcome, key decisions, and approvals..."
+ className="text-sm"
+ minHeight="160px"
+ data-testid="conclusion-remark-textarea"
+ />
+ )}
+
+ 💡 Tip: You can paste formatted content (lists, tables) and the formatting will be preserved.
+
+
+
+
This will be the final summary for this request
+
+ {conclusionRemark ? conclusionRemark.replace(/<[^>]*>/g, '').length : 0} / 2000 characters
+
+
+
+
+
+
Finalizing this request will:
+
+ Change request status to "CLOSED"
+ Notify all participants of closure
+ Move request to Closed Requests
+ Save conclusion remark permanently
+
+
+
+ {handleFinalizeConclusion && (
+
+
+ {conclusionSubmitting ? (
+ <>
+
+ Finalizing...
+ >
+ ) : (
+ <>
+
+ Finalize & Close Request
+ >
+ )}
+
+
+ )}
+
+ )}
+
+
+ )}
);
}
diff --git a/src/dealer-claim/components/request-detail/WorkflowTab.tsx b/src/dealer-claim/components/request-detail/WorkflowTab.tsx
index d22f6d4..eef998a 100644
--- a/src/dealer-claim/components/request-detail/WorkflowTab.tsx
+++ b/src/dealer-claim/components/request-detail/WorkflowTab.tsx
@@ -21,7 +21,7 @@ import { CreditNoteSAPModal } from './modals';
import { EmailNotificationTemplateModal } from './modals';
import { DMSPushModal } from './modals';
import { toast } from 'sonner';
-import { submitProposal, updateIODetails, submitCompletion, updateEInvoice } from '@/services/dealerClaimApi';
+import { submitProposal, updateIODetails, submitCompletion, updateEInvoice, sendCreditNoteToDealer } from '@/services/dealerClaimApi';
import { getWorkflowDetails, approveLevel, rejectLevel } from '@/services/workflowApi';
import { uploadDocument } from '@/services/documentApi';
@@ -1271,8 +1271,26 @@ export function DealerClaimWorkflowTab({
toast.info('Download functionality will be implemented');
}}
onSendToDealer={async () => {
- // TODO: Implement send to dealer functionality
- toast.info('Send to dealer functionality will be implemented');
+ try {
+ const requestId = request?.requestId || request?.id;
+ if (!requestId) {
+ toast.error('Request ID not found');
+ return;
+ }
+
+ await sendCreditNoteToDealer(requestId);
+
+ toast.success('Credit note sent to dealer successfully. Step 8 has been approved.');
+
+ // Refresh the request details to show updated status
+ if (onRefresh) {
+ onRefresh();
+ }
+ } catch (error: any) {
+ console.error('Failed to send credit note to dealer:', error);
+ const errorMessage = error?.response?.data?.message || error?.message || 'Failed to send credit note to dealer';
+ toast.error(errorMessage);
+ }
}}
creditNoteData={{
creditNoteNumber: (request as any)?.creditNote?.creditNoteNumber ||
@@ -1295,7 +1313,9 @@ export function DealerClaimWorkflowTab({
Number((request as any)?.claimDetails?.creditNoteAmount) :
((request as any)?.claimDetails?.credit_note_amount ?
Number((request as any)?.claimDetails?.credit_note_amount) : undefined)))),
- status: 'APPROVED',
+ status: (request as any)?.creditNote?.status ||
+ (request as any)?.claimDetails?.creditNote?.status ||
+ ((request as any)?.creditNote?.creditNoteNumber ? 'CONFIRMED' : 'PENDING'),
}}
dealerInfo={{
dealerName: (request as any)?.claimDetails?.dealerName || (request as any)?.claimDetails?.dealer_name,
diff --git a/src/dealer-claim/components/request-detail/claim-cards/CloserCard.tsx b/src/dealer-claim/components/request-detail/claim-cards/CloserCard.tsx
new file mode 100644
index 0000000..fb3cf80
--- /dev/null
+++ b/src/dealer-claim/components/request-detail/claim-cards/CloserCard.tsx
@@ -0,0 +1,118 @@
+/**
+ * CloserCard Component
+ * Displays who closed the request and closure details
+ */
+
+import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
+import { Avatar, AvatarFallback } from '@/components/ui/avatar';
+import { Mail, Calendar, FileText } from 'lucide-react';
+import { formatDateTime } from '@/utils/dateFormatter';
+
+interface CloserInfo {
+ name?: string;
+ role?: string;
+ department?: string;
+ email?: string;
+ closureDate?: string;
+ conclusionRemark?: string;
+}
+
+interface CloserCardProps {
+ closerInfo: CloserInfo;
+ className?: string;
+}
+
+export function CloserCard({ closerInfo, className }: CloserCardProps) {
+ // If no closure date, don't render
+ if (!closerInfo.closureDate) {
+ return null;
+ }
+
+ // Generate initials from name
+ const getInitials = (name?: string) => {
+ if (!name) return 'CL';
+ return name
+ .split(' ')
+ .map((n) => n[0])
+ .join('')
+ .toUpperCase()
+ .slice(0, 2);
+ };
+
+ const closerName = closerInfo.name || 'System';
+ const hasCloserInfo = closerInfo.name || closerInfo.email;
+
+ return (
+
+
+ Request Closer
+
+
+ {hasCloserInfo ? (
+
+
+
+ {getInitials(closerInfo.name)}
+
+
+
+
{closerName}
+ {closerInfo.role && (
+
{closerInfo.role}
+ )}
+ {closerInfo.department && (
+
{closerInfo.department}
+ )}
+
+
+ {/* Email */}
+ {closerInfo.email && (
+
+
+ {closerInfo.email}
+
+ )}
+
+ {/* Closure Date */}
+ {closerInfo.closureDate && (
+
+
+ Closed on {formatDateTime(closerInfo.closureDate, { includeTime: true, format: 'short' })}
+
+ )}
+
+
+
+ ) : (
+
+
Request closed by system
+ {closerInfo.closureDate && (
+
+
+ Closed on {formatDateTime(closerInfo.closureDate, { includeTime: true, format: 'short' })}
+
+ )}
+
+ )}
+
+ {/* Conclusion Remark */}
+ {closerInfo.conclusionRemark && (
+
+
+
+
+
+ Conclusion Remark
+
+
+ {closerInfo.conclusionRemark}
+
+
+
+
+ )}
+
+
+ );
+}
+
diff --git a/src/dealer-claim/components/request-detail/claim-cards/index.ts b/src/dealer-claim/components/request-detail/claim-cards/index.ts
index 655c336..b5da555 100644
--- a/src/dealer-claim/components/request-detail/claim-cards/index.ts
+++ b/src/dealer-claim/components/request-detail/claim-cards/index.ts
@@ -10,3 +10,4 @@ export { DealerInformationCard } from './DealerInformationCard';
export { ProcessDetailsCard } from './ProcessDetailsCard';
export { ProposalDetailsCard } from './ProposalDetailsCard';
export { RequestInitiatorCard } from './RequestInitiatorCard';
+export { CloserCard } from './CloserCard';
diff --git a/src/dealer-claim/components/request-detail/modals/CreditNoteSAPModal.tsx b/src/dealer-claim/components/request-detail/modals/CreditNoteSAPModal.tsx
index 579edca..aace86c 100644
--- a/src/dealer-claim/components/request-detail/modals/CreditNoteSAPModal.tsx
+++ b/src/dealer-claim/components/request-detail/modals/CreditNoteSAPModal.tsx
@@ -57,12 +57,13 @@ export function CreditNoteSAPModal({
const [downloading, setDownloading] = useState(false);
const [sending, setSending] = useState(false);
- const creditNoteNumber = creditNoteData?.creditNoteNumber || 'CN-RE-REQ-2024-CM-101-312580';
+ const hasCreditNote = creditNoteData?.creditNoteNumber && creditNoteData?.creditNoteNumber !== '';
+ const creditNoteNumber = creditNoteData?.creditNoteNumber || '';
const creditNoteDate = creditNoteData?.creditNoteDate
? formatDateTime(creditNoteData.creditNoteDate, { includeTime: false, format: 'short' })
- : 'Dec 5, 2025';
- const creditNoteAmount = creditNoteData?.creditNoteAmount || 800;
- const status = creditNoteData?.status || 'APPROVED';
+ : '';
+ const creditNoteAmount = creditNoteData?.creditNoteAmount || 0;
+ const status = creditNoteData?.status || 'PENDING';
const dealerName = dealerInfo?.dealerName || 'Jaipur Royal Enfield';
const dealerCode = dealerInfo?.dealerCode || 'RE-JP-009';
@@ -127,44 +128,63 @@ export function CreditNoteSAPModal({
- {/* Credit Note Document Card */}
-
-
-
-
Royal Enfield
-
Credit Note Document
+ {hasCreditNote ? (
+ <>
+ {/* Credit Note Document Card */}
+
+
+
+
Royal Enfield
+
Credit Note Document
+
+
+
+ {status === 'APPROVED' || status === 'CONFIRMED' ? 'Approved' : status === 'ISSUED' ? 'Issued' : status === 'SENT' ? 'Sent' : 'Pending'}
+
+
+
+
+
+
+ Credit Note Number
+
+
{creditNoteNumber}
+
+
+
+
+ Issue Date
+
+
{creditNoteDate}
+
+
-
-
- {status === 'APPROVED' ? 'Approved' : status === 'ISSUED' ? 'Issued' : status === 'SENT' ? 'Sent' : 'Pending'}
-
-
-
-
-
-
- Credit Note Number
-
-
{creditNoteNumber}
-
-
-
-
- Issue Date
-
-
{creditNoteDate}
-
-
-
- {/* Credit Note Amount */}
-
-
-
- Credit Note Amount
-
-
{formatCurrency(creditNoteAmount)}
-
+ {/* Credit Note Amount */}
+
+
+
+ Credit Note Amount
+
+
{formatCurrency(creditNoteAmount)}
+
+ >
+ ) : (
+ /* No Credit Note Available */
+
+
+
+
+
+
+
No Credit Note Available
+
+ Credit note has not been generated yet. Please wait for the credit note to be generated from DMS.
+
+
+
+
+ )}
{/* Dealer Information */}
@@ -244,23 +264,27 @@ export function CreditNoteSAPModal({
Close
-
-
- {downloading ? 'Downloading...' : 'Download'}
-
-
-
- {sending ? 'Sending...' : 'Send to Dealer'}
-
+ {hasCreditNote && (
+ <>
+
+
+ {downloading ? 'Downloading...' : 'Download'}
+
+
+
+ {sending ? 'Sending...' : 'Send to Dealer'}
+
+ >
+ )}
diff --git a/src/dealer-claim/pages/RequestDetail.tsx b/src/dealer-claim/pages/RequestDetail.tsx
index dfe3f35..29c4c26 100644
--- a/src/dealer-claim/pages/RequestDetail.tsx
+++ b/src/dealer-claim/pages/RequestDetail.tsx
@@ -36,6 +36,7 @@ import { useRequestDetails } from '@/hooks/useRequestDetails';
import { useRequestSocket } from '@/hooks/useRequestSocket';
import { useDocumentUpload } from '@/hooks/useDocumentUpload';
import { useModalManager } from '@/hooks/useModalManager';
+import { useConclusionRemark } from '@/hooks/useConclusionRemark';
import { downloadDocument } from '@/services/workflowApi';
// Dealer Claim Components (import from index to get properly aliased exports)
@@ -218,6 +219,37 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
handleAddSpectator,
} = useModalManager(requestIdentifier, currentApprovalLevel, refreshDetails);
+ // Closure functionality - only for initiator when request is approved/rejected
+ // Check both lowercase and uppercase status values
+ const requestStatus = (request?.status || apiRequest?.status || '').toLowerCase();
+ const needsClosure = (requestStatus === 'approved' || requestStatus === 'rejected') && isInitiator;
+
+ // Debug logging
+ console.debug('[DealerClaimRequestDetail] Closure check:', {
+ requestStatus,
+ requestStatusRaw: request?.status,
+ apiRequestStatusRaw: apiRequest?.status,
+ isInitiator,
+ needsClosure,
+ });
+ const {
+ conclusionRemark,
+ setConclusionRemark,
+ conclusionLoading,
+ conclusionSubmitting,
+ aiGenerated,
+ handleGenerateConclusion,
+ handleFinalizeConclusion,
+ } = useConclusionRemark(
+ request,
+ requestIdentifier,
+ isInitiator,
+ refreshDetails,
+ onBack,
+ setActionStatus,
+ setShowActionStatusModal
+ );
+
// Auto-switch tab when URL query parameter changes
useEffect(() => {
const urlParams = new URLSearchParams(window.location.search);
@@ -500,6 +532,14 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
apiRequest={apiRequest}
currentUserId={currentUserId}
isInitiator={isInitiator}
+ needsClosure={needsClosure}
+ conclusionRemark={conclusionRemark}
+ setConclusionRemark={setConclusionRemark}
+ conclusionLoading={conclusionLoading}
+ conclusionSubmitting={conclusionSubmitting}
+ aiGenerated={aiGenerated}
+ handleGenerateConclusion={handleGenerateConclusion}
+ handleFinalizeConclusion={handleFinalizeConclusion}
/>
diff --git a/src/services/dealerClaimApi.ts b/src/services/dealerClaimApi.ts
index c3e413b..cf93c85 100644
--- a/src/services/dealerClaimApi.ts
+++ b/src/services/dealerClaimApi.ts
@@ -290,3 +290,17 @@ export async function updateCreditNote(
}
}
+/**
+ * Send credit note to dealer and auto-approve Step 8
+ * POST /api/v1/dealer-claims/:requestId/credit-note/send
+ */
+export async function sendCreditNoteToDealer(requestId: string): Promise
{
+ try {
+ const response = await apiClient.post(`/dealer-claims/${requestId}/credit-note/send`);
+ return response.data?.data || response.data;
+ } catch (error: any) {
+ console.error('[DealerClaimAPI] Error sending credit note to dealer:', error);
+ throw error;
+ }
+}
+