unecessary consles removed approvr performance issue card redirection issue resolved. background image added on landing screen
This commit is contained in:
parent
164d576ea0
commit
985b755707
@ -28,6 +28,7 @@ import { CUSTOM_REQUEST_DATABASE } from '@/utils/customRequestDatabase';
|
|||||||
import { AuthCallback } from '@/pages/Auth/AuthCallback';
|
import { AuthCallback } from '@/pages/Auth/AuthCallback';
|
||||||
import { createClaimRequest } from '@/services/dealerClaimApi';
|
import { createClaimRequest } from '@/services/dealerClaimApi';
|
||||||
import { ManagerSelectionModal } from '@/components/modals/ManagerSelectionModal';
|
import { ManagerSelectionModal } from '@/components/modals/ManagerSelectionModal';
|
||||||
|
import { navigateToRequest } from '@/utils/requestNavigation';
|
||||||
import { TokenManager } from '@/utils/tokenManager';
|
import { TokenManager } from '@/utils/tokenManager';
|
||||||
|
|
||||||
interface AppProps {
|
interface AppProps {
|
||||||
@ -148,12 +149,11 @@ function AppRoutes({ onLogout }: AppProps) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleViewRequest = async (requestId: string, requestTitle?: string, status?: string, request?: any) => {
|
const handleViewRequest = (requestId: string, requestTitle?: string, status?: string, request?: any) => {
|
||||||
setSelectedRequestId(requestId);
|
setSelectedRequestId(requestId);
|
||||||
setSelectedRequestTitle(requestTitle || 'Unknown Request');
|
setSelectedRequestTitle(requestTitle || 'Unknown Request');
|
||||||
|
|
||||||
// Use global navigation utility for consistent routing
|
// Use global navigation utility for consistent routing
|
||||||
const { navigateToRequest } = await import('@/utils/requestNavigation');
|
|
||||||
navigateToRequest({
|
navigateToRequest({
|
||||||
requestId,
|
requestId,
|
||||||
requestTitle,
|
requestTitle,
|
||||||
|
|||||||
BIN
src/assets/images/landing_page_image.jpg
Normal file
BIN
src/assets/images/landing_page_image.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
@ -8,6 +8,7 @@
|
|||||||
// Images
|
// Images
|
||||||
export { default as ReLogo } from './images/Re_Logo.png';
|
export { default as ReLogo } from './images/Re_Logo.png';
|
||||||
export { default as RoyalEnfieldLogo } from './images/royal_enfield_logo.png';
|
export { default as RoyalEnfieldLogo } from './images/royal_enfield_logo.png';
|
||||||
|
export { default as LandingPageImage } from './images/landing_page_image.jpg';
|
||||||
|
|
||||||
// Fonts
|
// Fonts
|
||||||
// Add font exports here when fonts are added to the assets/fonts folder
|
// Add font exports here when fonts are added to the assets/fonts folder
|
||||||
|
|||||||
@ -91,13 +91,7 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
// Formula: remaining = availableBeforeBlock - blockedAmount
|
// Formula: remaining = availableBeforeBlock - blockedAmount
|
||||||
const expectedRemaining = availableBeforeBlock - blockedAmt;
|
const expectedRemaining = availableBeforeBlock - blockedAmt;
|
||||||
|
|
||||||
// Log for debugging backend calculation
|
// Loading existing IO block
|
||||||
console.log('[IOTab] Loading existing IO block:', {
|
|
||||||
availableBeforeBlock,
|
|
||||||
blockedAmount: blockedAmt,
|
|
||||||
expectedRemaining,
|
|
||||||
backendRemaining,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Warn if remaining balance calculation seems incorrect (for backend debugging)
|
// Warn if remaining balance calculation seems incorrect (for backend debugging)
|
||||||
if (Math.abs(backendRemaining - expectedRemaining) > 0.01) {
|
if (Math.abs(backendRemaining - expectedRemaining) > 0.01) {
|
||||||
@ -249,15 +243,7 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Log the amount being sent to backend for debugging
|
// Blocking budget
|
||||||
console.log('[IOTab] Blocking budget:', {
|
|
||||||
ioNumber: ioNumber.trim(),
|
|
||||||
originalInput: amountToBlock,
|
|
||||||
parsedAmount: blockAmountRaw,
|
|
||||||
roundedAmount: blockAmount,
|
|
||||||
fetchedAmount,
|
|
||||||
calculatedRemaining: fetchedAmount - blockAmount,
|
|
||||||
});
|
|
||||||
|
|
||||||
setBlockingBudget(true);
|
setBlockingBudget(true);
|
||||||
try {
|
try {
|
||||||
@ -272,7 +258,7 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
ioRemainingBalance: fetchedAmount - blockAmount, // Calculated value (backend will use SAP's actual value)
|
ioRemainingBalance: fetchedAmount - blockAmount, // Calculated value (backend will use SAP's actual value)
|
||||||
};
|
};
|
||||||
|
|
||||||
console.log('[IOTab] Sending to backend:', payload);
|
// Sending to backend
|
||||||
|
|
||||||
await updateIODetails(requestId, payload);
|
await updateIODetails(requestId, payload);
|
||||||
|
|
||||||
@ -287,16 +273,7 @@ export function IOTab({ request, apiRequest, onRefresh }: IOTabProps) {
|
|||||||
// Calculate expected remaining balance for validation/debugging
|
// Calculate expected remaining balance for validation/debugging
|
||||||
const expectedRemainingBalance = fetchedAmount - savedBlockedAmount;
|
const expectedRemainingBalance = fetchedAmount - savedBlockedAmount;
|
||||||
|
|
||||||
// Log what was saved vs what we sent
|
// Blocking result processed
|
||||||
console.log('[IOTab] Blocking result:', {
|
|
||||||
sentAmount: blockAmount,
|
|
||||||
savedBlockedAmount,
|
|
||||||
availableBalance: fetchedAmount,
|
|
||||||
expectedRemaining: expectedRemainingBalance,
|
|
||||||
backendRemaining: savedRemainingBalance,
|
|
||||||
difference: savedBlockedAmount - blockAmount,
|
|
||||||
remainingDifference: fetchedAmount - savedRemainingBalance,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Warn if the saved amount differs from what we sent
|
// Warn if the saved amount differs from what we sent
|
||||||
if (Math.abs(savedBlockedAmount - blockAmount) > 0.01) {
|
if (Math.abs(savedBlockedAmount - blockAmount) > 0.01) {
|
||||||
|
|||||||
@ -86,16 +86,7 @@ export function ClaimManagementOverviewTab({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Debug: Log mapped data for troubleshooting
|
// Mapped claim data ready
|
||||||
console.debug('[ClaimManagementOverviewTab] Mapped claim data:', {
|
|
||||||
activityInfo: claimRequest.activityInfo,
|
|
||||||
dealerInfo: claimRequest.dealerInfo,
|
|
||||||
hasProposalDetails: !!claimRequest.proposalDetails,
|
|
||||||
closedExpenses: claimRequest.activityInfo?.closedExpenses,
|
|
||||||
closedExpensesBreakdown: claimRequest.activityInfo?.closedExpensesBreakdown,
|
|
||||||
hasDealerCode: !!claimRequest.dealerInfo?.dealerCode,
|
|
||||||
hasDealerName: !!claimRequest.dealerInfo?.dealerName,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Determine user's role
|
// Determine user's role
|
||||||
const userRole: RequestRole = determineUserRole(apiRequest, currentUserId);
|
const userRole: RequestRole = determineUserRole(apiRequest, currentUserId);
|
||||||
@ -103,13 +94,7 @@ export function ClaimManagementOverviewTab({
|
|||||||
// Get visibility settings based on role
|
// Get visibility settings based on role
|
||||||
const visibility = getRoleBasedVisibility(userRole);
|
const visibility = getRoleBasedVisibility(userRole);
|
||||||
|
|
||||||
console.debug('[ClaimManagementOverviewTab] User role and visibility:', {
|
// User role and visibility determined
|
||||||
userRole,
|
|
||||||
visibility,
|
|
||||||
currentUserId,
|
|
||||||
showDealerInfo: visibility.showDealerInfo,
|
|
||||||
dealerInfoPresent: !!(claimRequest.dealerInfo?.dealerCode || claimRequest.dealerInfo?.dealerName),
|
|
||||||
});
|
|
||||||
|
|
||||||
// Extract initiator info from request
|
// Extract initiator info from request
|
||||||
// The apiRequest has initiator object with displayName, email, department, phone, etc.
|
// The apiRequest has initiator object with displayName, email, department, phone, etc.
|
||||||
@ -121,20 +106,7 @@ export function ClaimManagementOverviewTab({
|
|||||||
phone: apiRequest.initiator?.phone || apiRequest.initiator?.mobile,
|
phone: apiRequest.initiator?.phone || apiRequest.initiator?.mobile,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Debug: Log closure props to help troubleshoot
|
// Closure setup check completed
|
||||||
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 (
|
return (
|
||||||
<div className={`space-y-6 ${className}`}>
|
<div className={`space-y-6 ${className}`}>
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/com
|
|||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Badge } from '@/components/ui/badge';
|
import { Badge } from '@/components/ui/badge';
|
||||||
import { Progress } from '@/components/ui/progress';
|
import { Progress } from '@/components/ui/progress';
|
||||||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
||||||
import { PieChart, Pie, Cell, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
import { PieChart, Pie, Cell, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts';
|
||||||
import { getDealerDashboard, type DashboardKPIs as DashboardKPIsType, type CategoryData as CategoryDataType } from '@/services/dealerClaimApi';
|
import { getDealerDashboard, type DashboardKPIs as DashboardKPIsType, type CategoryData as CategoryDataType } from '@/services/dealerClaimApi';
|
||||||
import { RefreshCw } from 'lucide-react';
|
import { RefreshCw } from 'lucide-react';
|
||||||
@ -535,13 +534,7 @@ export function DealerDashboard({ onNavigate, onNewRequest: _onNewRequest }: Das
|
|||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
<Tabs defaultValue="overview" className="w-full">
|
<div className="space-y-4">
|
||||||
<TabsList className="grid w-full grid-cols-3">
|
|
||||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
|
||||||
<TabsTrigger value="category-1">Top Category 1</TabsTrigger>
|
|
||||||
<TabsTrigger value="category-2">Top Category 2</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<TabsContent value="overview" className="space-y-4 mt-6">
|
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-lg mb-4 text-gray-900">Activity Type Value Comparison</h3>
|
<h3 className="text-lg mb-4 text-gray-900">Activity Type Value Comparison</h3>
|
||||||
<ResponsiveContainer width="100%" height={350}>
|
<ResponsiveContainer width="100%" height={350}>
|
||||||
@ -610,16 +603,7 @@ export function DealerDashboard({ onNavigate, onNewRequest: _onNewRequest }: Das
|
|||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</div>
|
||||||
<TabsContent value="category-1" className="space-y-4">
|
|
||||||
{/* Category 1 details */}
|
|
||||||
<p className="text-gray-600">Detailed view for top category 1</p>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="category-2" className="space-y-4">
|
|
||||||
{/* Category 2 details */}
|
|
||||||
<p className="text-gray-600">Detailed view for top category 2</p>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|||||||
@ -245,14 +245,7 @@ function DealerClaimRequestDetailInner({ requestId: propRequestId, onBack, dynam
|
|||||||
const requestStatus = (request?.status || apiRequest?.status || '').toLowerCase();
|
const requestStatus = (request?.status || apiRequest?.status || '').toLowerCase();
|
||||||
const needsClosure = (requestStatus === 'approved' || requestStatus === 'rejected') && isInitiator;
|
const needsClosure = (requestStatus === 'approved' || requestStatus === 'rejected') && isInitiator;
|
||||||
|
|
||||||
// Debug logging
|
// Closure check completed
|
||||||
console.debug('[DealerClaimRequestDetail] Closure check:', {
|
|
||||||
requestStatus,
|
|
||||||
requestStatusRaw: request?.status,
|
|
||||||
apiRequestStatusRaw: apiRequest?.status,
|
|
||||||
isInitiator,
|
|
||||||
needsClosure,
|
|
||||||
});
|
|
||||||
const {
|
const {
|
||||||
conclusionRemark,
|
conclusionRemark,
|
||||||
setConclusionRemark,
|
setConclusionRemark,
|
||||||
|
|||||||
@ -50,26 +50,46 @@ export function useConclusionRemark(
|
|||||||
* Use Case: When request is approved, final approver generates conclusion.
|
* Use Case: When request is approved, final approver generates conclusion.
|
||||||
* Initiator needs to review and finalize it before closing request.
|
* Initiator needs to review and finalize it before closing request.
|
||||||
*
|
*
|
||||||
|
* Optimization: Check request object first before making API call
|
||||||
* Process:
|
* Process:
|
||||||
* 1. Dynamically import conclusion API service
|
* 1. Check if conclusion data is already in request object
|
||||||
* 2. Fetch conclusion by request ID
|
* 2. If not available, fetch from API
|
||||||
* 3. Load into state if exists
|
* 3. Load into state if exists
|
||||||
* 4. Mark as AI-generated if applicable
|
* 4. Mark as AI-generated if applicable
|
||||||
*/
|
*/
|
||||||
const fetchExistingConclusion = async () => {
|
const fetchExistingConclusion = async () => {
|
||||||
|
// Optimization: Check if conclusion data is already in request object
|
||||||
|
// Request detail response includes conclusionRemark and aiGeneratedConclusion fields
|
||||||
|
const existingConclusion = request?.conclusionRemark || request?.conclusion_remark;
|
||||||
|
const existingAiConclusion = request?.aiGeneratedConclusion || request?.ai_generated_conclusion;
|
||||||
|
|
||||||
|
if (existingConclusion || existingAiConclusion) {
|
||||||
|
// Use data from request object - no API call needed
|
||||||
|
setConclusionRemark(existingConclusion || existingAiConclusion);
|
||||||
|
setAiGenerated(!!existingAiConclusion);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only fetch from API if not available in request object
|
||||||
|
// This handles cases where request object might not have been refreshed yet
|
||||||
try {
|
try {
|
||||||
// Lazy load: Import conclusion API only when needed
|
// Lazy load: Import conclusion API only when needed
|
||||||
const { getConclusion } = await import('@/services/conclusionApi');
|
const { getConclusion } = await import('@/services/conclusionApi');
|
||||||
|
|
||||||
// API Call: Fetch existing conclusion
|
// API Call: Fetch existing conclusion (returns null if not found)
|
||||||
const result = await getConclusion(request.requestId || requestIdentifier);
|
const result = await getConclusion(request.requestId || requestIdentifier);
|
||||||
|
|
||||||
if (result && result.aiGeneratedRemark) {
|
if (result && (result.aiGeneratedRemark || result.finalRemark)) {
|
||||||
// Load: Set the AI-generated or final remark
|
// Load: Set the AI-generated or final remark
|
||||||
setConclusionRemark(result.finalRemark || result.aiGeneratedRemark);
|
// Handle null values by providing empty string fallback
|
||||||
|
setConclusionRemark(result.finalRemark || result.aiGeneratedRemark || '');
|
||||||
setAiGenerated(!!result.aiGeneratedRemark);
|
setAiGenerated(!!result.aiGeneratedRemark);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
// Only log non-404 errors (404 is handled gracefully in API)
|
||||||
|
if ((err as any)?.response?.status !== 404) {
|
||||||
|
console.error('[useConclusionRemark] Error fetching conclusion:', err);
|
||||||
|
}
|
||||||
// No conclusion yet - this is expected for newly approved requests
|
// No conclusion yet - this is expected for newly approved requests
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -218,16 +238,36 @@ export function useConclusionRemark(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Effect: Auto-fetch existing conclusion when request becomes approved or rejected
|
* Effect: Auto-load existing conclusion when request becomes approved, rejected, or closed
|
||||||
*
|
*
|
||||||
* Trigger: When request status changes to "approved" or "rejected" and user is initiator
|
* Trigger: When request status changes to "approved", "rejected", or "closed" and user is initiator
|
||||||
* Purpose: Load any conclusion generated by final approver (for approved) or AI (for rejected)
|
* Purpose: Load any conclusion generated by final approver (for approved) or AI (for rejected)
|
||||||
|
*
|
||||||
|
* Optimization:
|
||||||
|
* 1. First check if conclusion data is already in request object (no API call needed)
|
||||||
|
* 2. Only fetch from API if not available in request object
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if ((request?.status === 'approved' || request?.status === 'rejected') && isInitiator && !conclusionRemark) {
|
const status = request?.status?.toLowerCase();
|
||||||
|
const shouldLoad = (status === 'approved' || status === 'rejected' || status === 'closed')
|
||||||
|
&& isInitiator
|
||||||
|
&& !conclusionRemark;
|
||||||
|
|
||||||
|
if (!shouldLoad) return;
|
||||||
|
|
||||||
|
// Check if conclusion data is already in request object
|
||||||
|
const existingConclusion = request?.conclusionRemark || request?.conclusion_remark;
|
||||||
|
const existingAiConclusion = request?.aiGeneratedConclusion || request?.ai_generated_conclusion;
|
||||||
|
|
||||||
|
if (existingConclusion || existingAiConclusion) {
|
||||||
|
// Use data from request object - no API call needed
|
||||||
|
setConclusionRemark(existingConclusion || existingAiConclusion);
|
||||||
|
setAiGenerated(!!existingAiConclusion);
|
||||||
|
} else {
|
||||||
|
// Only fetch from API if not available in request object
|
||||||
fetchExistingConclusion();
|
fetchExistingConclusion();
|
||||||
}
|
}
|
||||||
}, [request?.status, isInitiator]);
|
}, [request?.status, request?.conclusionRemark, request?.aiGeneratedConclusion, isInitiator, conclusionRemark]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
conclusionRemark,
|
conclusionRemark,
|
||||||
|
|||||||
@ -219,15 +219,18 @@ export function useRequestDetails(
|
|||||||
: [];
|
: [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch: Get pause details if request is paused
|
* Fetch: Get pause details only if request is actually paused
|
||||||
* This is needed to show resume/retrigger buttons correctly
|
* Use request-level isPaused field from workflow response
|
||||||
*/
|
*/
|
||||||
let pauseInfo = null;
|
let pauseInfo = null;
|
||||||
|
const isPaused = (wf as any).isPaused || false;
|
||||||
|
|
||||||
|
if (isPaused) {
|
||||||
try {
|
try {
|
||||||
pauseInfo = await getPauseDetails(wf.requestId);
|
pauseInfo = await getPauseDetails(wf.requestId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Pause info not available or request not paused - ignore
|
// Pause info not available - ignore
|
||||||
console.debug('Pause details not available:', error);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -240,24 +243,9 @@ export function useRequestDetails(
|
|||||||
|
|
||||||
if (wf.workflowType === 'CLAIM_MANAGEMENT' || wf.templateType === 'claim-management') {
|
if (wf.workflowType === 'CLAIM_MANAGEMENT' || wf.templateType === 'claim-management') {
|
||||||
try {
|
try {
|
||||||
console.debug('[useRequestDetails] Fetching claim details for requestId:', wf.requestId);
|
|
||||||
const claimResponse = await apiClient.get(`/dealer-claims/${wf.requestId}`);
|
const claimResponse = await apiClient.get(`/dealer-claims/${wf.requestId}`);
|
||||||
console.debug('[useRequestDetails] Claim API response:', {
|
|
||||||
status: claimResponse.status,
|
|
||||||
hasData: !!claimResponse.data,
|
|
||||||
dataKeys: claimResponse.data ? Object.keys(claimResponse.data) : [],
|
|
||||||
fullResponse: claimResponse.data,
|
|
||||||
});
|
|
||||||
|
|
||||||
const claimData = claimResponse.data?.data || claimResponse.data;
|
const claimData = claimResponse.data?.data || claimResponse.data;
|
||||||
console.debug('[useRequestDetails] Extracted claimData:', {
|
|
||||||
hasClaimData: !!claimData,
|
|
||||||
claimDataKeys: claimData ? Object.keys(claimData) : [],
|
|
||||||
hasClaimDetails: !!(claimData?.claimDetails || claimData?.claim_details),
|
|
||||||
hasProposalDetails: !!(claimData?.proposalDetails || claimData?.proposal_details),
|
|
||||||
hasCompletionDetails: !!(claimData?.completionDetails || claimData?.completion_details),
|
|
||||||
hasInternalOrder: !!(claimData?.internalOrder || claimData?.internal_order),
|
|
||||||
});
|
|
||||||
|
|
||||||
if (claimData) {
|
if (claimData) {
|
||||||
claimDetails = claimData.claimDetails || claimData.claim_details;
|
claimDetails = claimData.claimDetails || claimData.claim_details;
|
||||||
@ -278,24 +266,7 @@ export function useRequestDetails(
|
|||||||
(claimDetails as any).completionExpenses = completionExpenses;
|
(claimDetails as any).completionExpenses = completionExpenses;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('[useRequestDetails] Extracted details:', {
|
// Extracted details processed
|
||||||
claimDetails: claimDetails ? {
|
|
||||||
hasActivityName: !!(claimDetails.activityName || claimDetails.activity_name),
|
|
||||||
hasActivityType: !!(claimDetails.activityType || claimDetails.activity_type),
|
|
||||||
hasLocation: !!(claimDetails.location),
|
|
||||||
activityName: claimDetails.activityName || claimDetails.activity_name,
|
|
||||||
activityType: claimDetails.activityType || claimDetails.activity_type,
|
|
||||||
location: claimDetails.location,
|
|
||||||
allKeys: Object.keys(claimDetails),
|
|
||||||
} : null,
|
|
||||||
hasProposalDetails: !!proposalDetails,
|
|
||||||
hasCompletionDetails: !!completionDetails,
|
|
||||||
hasInternalOrder: !!internalOrder,
|
|
||||||
hasBudgetTracking: !!budgetTracking,
|
|
||||||
hasInvoice: !!invoice,
|
|
||||||
hasCreditNote: !!creditNote,
|
|
||||||
hasCompletionExpenses: Array.isArray(completionExpenses) && completionExpenses.length > 0,
|
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
console.warn('[useRequestDetails] No claimData found in response');
|
console.warn('[useRequestDetails] No claimData found in response');
|
||||||
}
|
}
|
||||||
@ -528,13 +499,17 @@ export function useRequestDetails(
|
|||||||
})
|
})
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
// Fetch pause details
|
// Fetch pause details only if request is actually paused
|
||||||
|
// Use request-level isPaused field from workflow response
|
||||||
let pauseInfo = null;
|
let pauseInfo = null;
|
||||||
|
const isPaused = (wf as any).isPaused || false;
|
||||||
|
|
||||||
|
if (isPaused) {
|
||||||
try {
|
try {
|
||||||
pauseInfo = await getPauseDetails(wf.requestId);
|
pauseInfo = await getPauseDetails(wf.requestId);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// Pause info not available or request not paused - ignore
|
// Pause info not available - ignore
|
||||||
console.debug('Pause details not available:', error);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -547,13 +522,7 @@ export function useRequestDetails(
|
|||||||
|
|
||||||
if (wf.workflowType === 'CLAIM_MANAGEMENT' || wf.templateType === 'claim-management') {
|
if (wf.workflowType === 'CLAIM_MANAGEMENT' || wf.templateType === 'claim-management') {
|
||||||
try {
|
try {
|
||||||
console.debug('[useRequestDetails] Initial load - Fetching claim details for requestId:', wf.requestId);
|
|
||||||
const claimResponse = await apiClient.get(`/dealer-claims/${wf.requestId}`);
|
const claimResponse = await apiClient.get(`/dealer-claims/${wf.requestId}`);
|
||||||
console.debug('[useRequestDetails] Initial load - Claim API response:', {
|
|
||||||
status: claimResponse.status,
|
|
||||||
hasData: !!claimResponse.data,
|
|
||||||
dataKeys: claimResponse.data ? Object.keys(claimResponse.data) : [],
|
|
||||||
});
|
|
||||||
|
|
||||||
const claimData = claimResponse.data?.data || claimResponse.data;
|
const claimData = claimResponse.data?.data || claimResponse.data;
|
||||||
if (claimData) {
|
if (claimData) {
|
||||||
@ -575,17 +544,7 @@ export function useRequestDetails(
|
|||||||
(claimDetails as any).completionExpenses = completionExpenses;
|
(claimDetails as any).completionExpenses = completionExpenses;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.debug('[useRequestDetails] Initial load - Extracted details:', {
|
// Initial load - Extracted details processed
|
||||||
hasClaimDetails: !!claimDetails,
|
|
||||||
claimDetailsKeys: claimDetails ? Object.keys(claimDetails) : [],
|
|
||||||
hasProposalDetails: !!proposalDetails,
|
|
||||||
hasCompletionDetails: !!completionDetails,
|
|
||||||
hasInternalOrder: !!internalOrder,
|
|
||||||
hasBudgetTracking: !!budgetTracking,
|
|
||||||
hasInvoice: !!invoice,
|
|
||||||
hasCreditNote: !!creditNote,
|
|
||||||
hasCompletionExpenses: Array.isArray(completionExpenses) && completionExpenses.length > 0,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
// Claim details not available - request might not be fully initialized yet
|
// Claim details not available - request might not be fully initialized yet
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import { Pagination } from '@/components/common/Pagination';
|
|||||||
import { getPriorityConfig, getStatusConfig, getSLAConfig } from '../utils/configMappers';
|
import { getPriorityConfig, getStatusConfig, getSLAConfig } from '../utils/configMappers';
|
||||||
import { formatDate, formatDateTime } from '../utils/formatters';
|
import { formatDate, formatDateTime } from '../utils/formatters';
|
||||||
import { formatHoursMinutes } from '@/utils/slaTracker';
|
import { formatHoursMinutes } from '@/utils/slaTracker';
|
||||||
|
import { navigateToRequest } from '@/utils/requestNavigation';
|
||||||
import type { ApproverPerformanceRequest } from '../types/approverPerformance.types';
|
import type { ApproverPerformanceRequest } from '../types/approverPerformance.types';
|
||||||
|
|
||||||
interface ApproverPerformanceRequestListProps {
|
interface ApproverPerformanceRequestListProps {
|
||||||
@ -69,7 +70,6 @@ export function ApproverPerformanceRequestList({
|
|||||||
key={request.requestId}
|
key={request.requestId}
|
||||||
className="hover:shadow-md transition-shadow cursor-pointer"
|
className="hover:shadow-md transition-shadow cursor-pointer"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const { navigateToRequest } = require('@/utils/requestNavigation');
|
|
||||||
navigateToRequest({
|
navigateToRequest({
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
requestTitle: request.title,
|
requestTitle: request.title,
|
||||||
@ -166,7 +166,6 @@ export function ApproverPerformanceRequestList({
|
|||||||
size="sm"
|
size="sm"
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
const { navigateToRequest } = require('@/utils/requestNavigation');
|
|
||||||
navigateToRequest({
|
navigateToRequest({
|
||||||
requestId: request.requestId,
|
requestId: request.requestId,
|
||||||
requestTitle: request.title,
|
requestTitle: request.title,
|
||||||
|
|||||||
@ -1,14 +1,28 @@
|
|||||||
import { useState } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
import { useAuth } from '@/contexts/AuthContext';
|
import { useAuth } from '@/contexts/AuthContext';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader } from '@/components/ui/card';
|
||||||
import { LogIn, Shield } from 'lucide-react';
|
import { LogIn, Shield } from 'lucide-react';
|
||||||
import { ReLogo } from '@/assets';
|
import { ReLogo, LandingPageImage } from '@/assets';
|
||||||
import { initiateTanflowLogin } from '@/services/tanflowAuth';
|
import { initiateTanflowLogin } from '@/services/tanflowAuth';
|
||||||
|
|
||||||
export function Auth() {
|
export function Auth() {
|
||||||
const { login, isLoading, error } = useAuth();
|
const { login, isLoading, error } = useAuth();
|
||||||
const [tanflowLoading, setTanflowLoading] = useState(false);
|
const [tanflowLoading, setTanflowLoading] = useState(false);
|
||||||
|
const [imageLoaded, setImageLoaded] = useState(false);
|
||||||
|
|
||||||
|
// Preload the background image
|
||||||
|
useEffect(() => {
|
||||||
|
const img = new Image();
|
||||||
|
img.src = LandingPageImage;
|
||||||
|
img.onload = () => {
|
||||||
|
setImageLoaded(true);
|
||||||
|
};
|
||||||
|
// If image is already cached, trigger load immediately
|
||||||
|
if (img.complete) {
|
||||||
|
setImageLoaded(true);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
const handleOKTALogin = async () => {
|
const handleOKTALogin = async () => {
|
||||||
// Clear any existing session data
|
// Clear any existing session data
|
||||||
@ -51,8 +65,24 @@ export function Auth() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen flex items-center justify-center bg-gradient-to-br from-slate-50 to-slate-100 p-4">
|
<div
|
||||||
<Card className="w-full max-w-md shadow-xl">
|
className="min-h-screen flex items-center justify-center p-4 relative"
|
||||||
|
style={{
|
||||||
|
backgroundImage: imageLoaded ? `url(${LandingPageImage})` : 'none',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
|
transition: 'background-image 0.3s ease-in-out'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{/* Fallback background while image loads */}
|
||||||
|
{!imageLoaded && (
|
||||||
|
<div className="absolute inset-0 bg-gradient-to-br from-slate-900 to-slate-800"></div>
|
||||||
|
)}
|
||||||
|
{/* Overlay for better readability */}
|
||||||
|
<div className="absolute inset-0 bg-black/40"></div>
|
||||||
|
|
||||||
|
<Card className="w-full max-w-md shadow-xl relative z-10 bg-black backdrop-blur-sm border-gray-800">
|
||||||
<CardHeader className="space-y-1 text-center pb-6">
|
<CardHeader className="space-y-1 text-center pb-6">
|
||||||
<div className="flex flex-col items-center justify-center mb-4">
|
<div className="flex flex-col items-center justify-center mb-4">
|
||||||
<img
|
<img
|
||||||
@ -60,13 +90,13 @@ export function Auth() {
|
|||||||
alt="Royal Enfield Logo"
|
alt="Royal Enfield Logo"
|
||||||
className="h-10 w-auto max-w-[168px] object-contain mb-2"
|
className="h-10 w-auto max-w-[168px] object-contain mb-2"
|
||||||
/>
|
/>
|
||||||
<p className="text-xs text-gray-400 text-center truncate">Approval Portal</p>
|
<p className="text-xs text-gray-300 text-center truncate">Approval Portal</p>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
|
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
{error && (
|
{error && (
|
||||||
<div className="bg-red-50 border border-red-200 text-red-700 px-4 py-3 rounded-lg">
|
<div className="bg-red-900/50 border border-red-700 text-red-200 px-4 py-3 rounded-lg">
|
||||||
<p className="text-sm font-medium">Authentication Error</p>
|
<p className="text-sm font-medium">Authentication Error</p>
|
||||||
<p className="text-sm">{error.message}</p>
|
<p className="text-sm">{error.message}</p>
|
||||||
</div>
|
</div>
|
||||||
@ -96,10 +126,10 @@ export function Auth() {
|
|||||||
|
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute inset-0 flex items-center">
|
<div className="absolute inset-0 flex items-center">
|
||||||
<span className="w-full border-t border-gray-300"></span>
|
<span className="w-full border-t border-gray-700"></span>
|
||||||
</div>
|
</div>
|
||||||
<div className="relative flex justify-center text-xs uppercase">
|
<div className="relative flex justify-center text-xs uppercase">
|
||||||
<span className="bg-white px-2 text-gray-500">Or</span>
|
<span className="bg-gray-900 px-2 text-gray-400">Or</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -125,9 +155,9 @@ export function Auth() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-center text-sm text-gray-500 mt-4">
|
<div className="text-center text-sm text-gray-400 mt-4">
|
||||||
<p>Secure Single Sign-On</p>
|
<p>Secure Single Sign-On</p>
|
||||||
<p className="text-xs mt-1">Choose your authentication provider</p>
|
<p className="text-xs mt-1 text-gray-500">Choose your authentication provider</p>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import { useState, useCallback } from 'react';
|
import { useState, useCallback } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { navigateToRequest } from '@/utils/requestNavigation';
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import { DetailedReportsHeader } from './components/DetailedReportsHeader';
|
import { DetailedReportsHeader } from './components/DetailedReportsHeader';
|
||||||
@ -69,7 +70,6 @@ export function DetailedReports({ onBack }: DetailedReportsProps) {
|
|||||||
}, [onBack, navigate]);
|
}, [onBack, navigate]);
|
||||||
|
|
||||||
const handleViewRequest = useCallback((requestId: string) => {
|
const handleViewRequest = useCallback((requestId: string) => {
|
||||||
const { navigateToRequest } = require('@/utils/requestNavigation');
|
|
||||||
navigateToRequest({
|
navigateToRequest({
|
||||||
requestId,
|
requestId,
|
||||||
navigate,
|
navigate,
|
||||||
|
|||||||
@ -42,11 +42,15 @@ export function Notifications({ onNavigate }: NotificationsProps) {
|
|||||||
const result = await notificationApi.list({ page, limit: ITEMS_PER_PAGE, unreadOnly });
|
const result = await notificationApi.list({ page, limit: ITEMS_PER_PAGE, unreadOnly });
|
||||||
|
|
||||||
const notifs = result.data?.notifications || [];
|
const notifs = result.data?.notifications || [];
|
||||||
const total = result.data?.total || 0;
|
// Extract pagination data from the response
|
||||||
|
const pagination = result.data?.pagination || {};
|
||||||
|
const total = pagination.total || 0;
|
||||||
|
const totalPages = pagination.totalPages || 1;
|
||||||
|
|
||||||
setNotifications(notifs);
|
setNotifications(notifs);
|
||||||
setTotalCount(total);
|
setTotalCount(total);
|
||||||
setTotalPages(Math.ceil(total / ITEMS_PER_PAGE));
|
setTotalPages(totalPages);
|
||||||
|
setCurrentPage(page); // Update current page to match what was fetched
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Notifications] Failed to fetch:', error);
|
console.error('[Notifications] Failed to fetch:', error);
|
||||||
} finally {
|
} finally {
|
||||||
@ -56,6 +60,7 @@ export function Notifications({ onNavigate }: NotificationsProps) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setCurrentPage(1); // Reset to page 1 when filter changes
|
||||||
fetchNotifications(1, filter === 'unread');
|
fetchNotifications(1, filter === 'unread');
|
||||||
}, [filter]);
|
}, [filter]);
|
||||||
|
|
||||||
@ -82,6 +87,11 @@ export function Notifications({ onNavigate }: NotificationsProps) {
|
|||||||
navigationUrl += '?tab=worknotes';
|
navigationUrl += '?tab=worknotes';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Document added notifications should open Documents tab
|
||||||
|
if (notification.notificationType === 'document_added') {
|
||||||
|
navigationUrl += '?tab=documents';
|
||||||
|
}
|
||||||
|
|
||||||
onNavigate(navigationUrl);
|
onNavigate(navigationUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -131,6 +141,8 @@ export function Notifications({ onNavigate }: NotificationsProps) {
|
|||||||
return <MessageSquare className={`${iconClass} text-blue-600`} />;
|
return <MessageSquare className={`${iconClass} text-blue-600`} />;
|
||||||
case 'worknote':
|
case 'worknote':
|
||||||
return <FileText className={`${iconClass} text-purple-600`} />;
|
return <FileText className={`${iconClass} text-purple-600`} />;
|
||||||
|
case 'document_added':
|
||||||
|
return <FileText className={`${iconClass} text-teal-600`} />;
|
||||||
case 'assignment':
|
case 'assignment':
|
||||||
return <UserPlus className={`${iconClass} text-indigo-600`} />;
|
return <UserPlus className={`${iconClass} text-indigo-600`} />;
|
||||||
case 'approval':
|
case 'approval':
|
||||||
|
|||||||
@ -57,9 +57,19 @@ export async function finalizeConclusion(requestId: string, finalRemark: string)
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Get conclusion for a request
|
* Get conclusion for a request
|
||||||
|
* Returns null if conclusion doesn't exist (404) instead of throwing error
|
||||||
*/
|
*/
|
||||||
export async function getConclusion(requestId: string): Promise<ConclusionRemark> {
|
export async function getConclusion(requestId: string): Promise<ConclusionRemark | null> {
|
||||||
|
try {
|
||||||
const response = await apiClient.get(`/conclusions/${requestId}`);
|
const response = await apiClient.get(`/conclusions/${requestId}`);
|
||||||
return response.data.data;
|
return response.data.data;
|
||||||
|
} catch (error: any) {
|
||||||
|
// Handle 404 gracefully - conclusion doesn't exist yet, which is normal
|
||||||
|
if (error.response?.status === 404) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
// Re-throw other errors
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -115,18 +115,7 @@ export function mapToClaimManagementRequest(
|
|||||||
const creditNote = apiRequest.creditNote || apiRequest.credit_note || {};
|
const creditNote = apiRequest.creditNote || apiRequest.credit_note || {};
|
||||||
const completionExpenses = apiRequest.completionExpenses || apiRequest.completion_expenses || [];
|
const completionExpenses = apiRequest.completionExpenses || apiRequest.completion_expenses || [];
|
||||||
|
|
||||||
// Debug: Log raw claim details to help troubleshoot
|
// Raw claim details processed
|
||||||
console.debug('[claimDataMapper] Raw claimDetails:', claimDetails);
|
|
||||||
console.debug('[claimDataMapper] Raw apiRequest:', {
|
|
||||||
hasClaimDetails: !!apiRequest.claimDetails,
|
|
||||||
hasProposalDetails: !!apiRequest.proposalDetails,
|
|
||||||
hasCompletionDetails: !!apiRequest.completionDetails,
|
|
||||||
hasBudgetTracking: !!budgetTracking,
|
|
||||||
hasInvoice: !!invoice,
|
|
||||||
hasCreditNote: !!creditNote,
|
|
||||||
hasCompletionExpenses: Array.isArray(completionExpenses) && completionExpenses.length > 0,
|
|
||||||
workflowType: apiRequest.workflowType,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Map activity information (matching ActivityInformationCard expectations)
|
// Map activity information (matching ActivityInformationCard expectations)
|
||||||
// Handle both camelCase and snake_case field names from Sequelize
|
// Handle both camelCase and snake_case field names from Sequelize
|
||||||
@ -137,14 +126,7 @@ export function mapToClaimManagementRequest(
|
|||||||
const activityType = claimDetails.activityType || claimDetails.activity_type || '';
|
const activityType = claimDetails.activityType || claimDetails.activity_type || '';
|
||||||
const location = claimDetails.location || '';
|
const location = claimDetails.location || '';
|
||||||
|
|
||||||
console.debug('[claimDataMapper] Mapped activity fields:', {
|
// Activity fields mapped
|
||||||
activityName,
|
|
||||||
activityType,
|
|
||||||
location,
|
|
||||||
hasActivityName: !!activityName,
|
|
||||||
hasActivityType: !!activityType,
|
|
||||||
hasLocation: !!location,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get budget values from budgetTracking table (new source of truth)
|
// Get budget values from budgetTracking table (new source of truth)
|
||||||
const estimatedBudget = budgetTracking.proposalEstimatedBudget ||
|
const estimatedBudget = budgetTracking.proposalEstimatedBudget ||
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user