231 lines
7.5 KiB
TypeScript
231 lines
7.5 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
|
|
/**
|
|
* Custom Hook: useConclusionRemark
|
|
*
|
|
* Purpose: Manages conclusion remark generation and finalization
|
|
*
|
|
* Responsibilities:
|
|
* - Fetches existing AI-generated conclusion
|
|
* - Generates new conclusion using AI
|
|
* - Finalizes conclusion and closes request
|
|
* - Manages loading and submission states
|
|
* - Handles navigation after successful closure
|
|
*
|
|
* @param request - Current request object
|
|
* @param requestIdentifier - Request number or UUID
|
|
* @param isInitiator - Whether current user is the request initiator
|
|
* @param refreshDetails - Function to refresh request data
|
|
* @param onBack - Navigation callback
|
|
* @param setActionStatus - Function to show action status modal
|
|
* @param setShowActionStatusModal - Function to control action status modal visibility
|
|
* @returns Object with conclusion state and action handlers
|
|
*/
|
|
export function useConclusionRemark(
|
|
request: any,
|
|
requestIdentifier: string,
|
|
isInitiator: boolean,
|
|
refreshDetails: () => Promise<void>,
|
|
onBack?: () => void,
|
|
setActionStatus?: (status: { success: boolean; title: string; message: string }) => void,
|
|
setShowActionStatusModal?: (show: boolean) => void
|
|
) {
|
|
// State: The conclusion remark text (editable by user)
|
|
const [conclusionRemark, setConclusionRemark] = useState('');
|
|
|
|
// State: Indicates if AI is currently generating conclusion
|
|
const [conclusionLoading, setConclusionLoading] = useState(false);
|
|
|
|
// State: Indicates if conclusion is being submitted to backend
|
|
const [conclusionSubmitting, setConclusionSubmitting] = useState(false);
|
|
|
|
// State: Tracks if current conclusion was AI-generated (shows badge in UI)
|
|
const [aiGenerated, setAiGenerated] = useState(false);
|
|
|
|
/**
|
|
* Function: fetchExistingConclusion
|
|
*
|
|
* Purpose: Load existing AI-generated conclusion from backend
|
|
*
|
|
* Use Case: When request is approved, final approver generates conclusion.
|
|
* Initiator needs to review and finalize it before closing request.
|
|
*
|
|
* Process:
|
|
* 1. Dynamically import conclusion API service
|
|
* 2. Fetch conclusion by request ID
|
|
* 3. Load into state if exists
|
|
* 4. Mark as AI-generated if applicable
|
|
*/
|
|
const fetchExistingConclusion = async () => {
|
|
try {
|
|
// Lazy load: Import conclusion API only when needed
|
|
const { getConclusion } = await import('@/services/conclusionApi');
|
|
|
|
// API Call: Fetch existing conclusion
|
|
const result = await getConclusion(request.requestId || requestIdentifier);
|
|
|
|
if (result && result.aiGeneratedRemark) {
|
|
// Load: Set the AI-generated or final remark
|
|
setConclusionRemark(result.finalRemark || result.aiGeneratedRemark);
|
|
setAiGenerated(!!result.aiGeneratedRemark);
|
|
}
|
|
} catch (err) {
|
|
// No conclusion yet - this is expected for newly approved requests
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: handleGenerateConclusion
|
|
*
|
|
* Purpose: Generate a new conclusion remark using AI
|
|
*
|
|
* How it works:
|
|
* 1. Sends request details to AI service
|
|
* 2. AI analyzes approval history, comments, and request data
|
|
* 3. Generates professional conclusion summarizing outcome
|
|
* 4. User can edit the AI suggestion before finalizing
|
|
*
|
|
* Process:
|
|
* 1. Set loading state
|
|
* 2. Call AI generation API
|
|
* 3. Load generated text into textarea
|
|
* 4. Mark as AI-generated (shows badge)
|
|
* 5. Handle errors silently (user can type manually)
|
|
*/
|
|
const handleGenerateConclusion = async () => {
|
|
try {
|
|
setConclusionLoading(true);
|
|
|
|
// Lazy load: Import conclusion API
|
|
const { generateConclusion } = await import('@/services/conclusionApi');
|
|
|
|
// API Call: Generate AI conclusion based on request data
|
|
const result = await generateConclusion(request.requestId || requestIdentifier);
|
|
|
|
// Success: Load AI-generated remark
|
|
setConclusionRemark(result.aiGeneratedRemark);
|
|
setAiGenerated(true);
|
|
} catch (err) {
|
|
// Fail silently: User can write conclusion manually
|
|
console.error('[useConclusionRemark] AI generation failed:', err);
|
|
setConclusionRemark('');
|
|
setAiGenerated(false);
|
|
} finally {
|
|
setConclusionLoading(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Function: handleFinalizeConclusion
|
|
*
|
|
* Purpose: Submit conclusion remark and close the request
|
|
*
|
|
* Business Logic:
|
|
* - Only initiators can finalize approved requests
|
|
* - Conclusion cannot be empty
|
|
* - After finalization:
|
|
* → Request status changes to CLOSED
|
|
* → All participants are notified
|
|
* → Request moves to Closed Requests
|
|
* → Conclusion is permanently saved
|
|
*
|
|
* Process:
|
|
* 1. Validate conclusion is not empty
|
|
* 2. Submit to backend
|
|
* 3. Show success modal
|
|
* 4. Refresh request data (status will be "closed")
|
|
* 5. Navigate to Closed Requests after 2 seconds
|
|
* 6. Handle errors with user-friendly messages
|
|
*/
|
|
const handleFinalizeConclusion = async () => {
|
|
// Validation: Ensure conclusion is not empty
|
|
if (!conclusionRemark.trim()) {
|
|
setActionStatus?.({
|
|
success: false,
|
|
title: 'Validation Error',
|
|
message: 'Conclusion remark cannot be empty'
|
|
});
|
|
setShowActionStatusModal?.(true);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
setConclusionSubmitting(true);
|
|
|
|
// Lazy load: Import conclusion API
|
|
const { finalizeConclusion } = await import('@/services/conclusionApi');
|
|
|
|
// API Call: Submit conclusion and close request
|
|
// Backend will:
|
|
// - Update request status to CLOSED
|
|
// - Save conclusion remark
|
|
// - Send notifications to all participants
|
|
// - Record closure timestamp
|
|
await finalizeConclusion(request.requestId || requestIdentifier, conclusionRemark);
|
|
|
|
// Success feedback
|
|
setActionStatus?.({
|
|
success: true,
|
|
title: 'Request Closed with Successful Completion',
|
|
message: 'The request has been finalized and moved to Closed Requests.'
|
|
});
|
|
setShowActionStatusModal?.(true);
|
|
|
|
// Refresh: Update UI with new "closed" status
|
|
await refreshDetails();
|
|
|
|
/**
|
|
* Navigate: Redirect to Closed Requests after showing success message
|
|
* Delay allows user to see the success notification
|
|
*/
|
|
setTimeout(() => {
|
|
if (onBack) {
|
|
// Use callback navigation if provided
|
|
onBack();
|
|
// Then navigate to closed requests
|
|
setTimeout(() => {
|
|
window.location.hash = '#/closed-requests';
|
|
}, 100);
|
|
} else {
|
|
// Direct navigation
|
|
window.location.hash = '#/closed-requests';
|
|
}
|
|
}, 2000); // 2 second delay
|
|
|
|
} catch (err: any) {
|
|
// Error feedback with backend message
|
|
setActionStatus?.({
|
|
success: false,
|
|
title: 'Error',
|
|
message: err.response?.data?.error || 'Failed to finalize conclusion'
|
|
});
|
|
setShowActionStatusModal?.(true);
|
|
} finally {
|
|
setConclusionSubmitting(false);
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Effect: Auto-fetch existing conclusion when request becomes approved
|
|
*
|
|
* Trigger: When request status changes to "approved" and user is initiator
|
|
* Purpose: Load any conclusion generated by final approver
|
|
*/
|
|
useEffect(() => {
|
|
if (request?.status === 'approved' && isInitiator && !conclusionRemark) {
|
|
fetchExistingConclusion();
|
|
}
|
|
}, [request?.status, isInitiator]);
|
|
|
|
return {
|
|
conclusionRemark,
|
|
setConclusionRemark,
|
|
conclusionLoading,
|
|
conclusionSubmitting,
|
|
aiGenerated,
|
|
handleGenerateConclusion,
|
|
handleFinalizeConclusion
|
|
};
|
|
}
|
|
|