Blocked Amount
diff --git a/src/dealer-claim/components/request-detail/WorkflowTab.tsx b/src/dealer-claim/components/request-detail/WorkflowTab.tsx
index eef998a..8795204 100644
--- a/src/dealer-claim/components/request-detail/WorkflowTab.tsx
+++ b/src/dealer-claim/components/request-detail/WorkflowTab.tsx
@@ -172,15 +172,11 @@ export function DealerClaimWorkflowTab({
const [refreshTrigger, setRefreshTrigger] = useState(0);
// Reload approval flows whenever request changes or after refresh
+ // Always fetch from API to ensure fresh data (don't rely on cached request.approvalFlow)
+ // Also watch for changes in totalLevels to detect when approvers are added
useEffect(() => {
const loadApprovalFlows = async () => {
- // First check if request has approvalFlow
- if (request?.approvalFlow && request.approvalFlow.length > 0) {
- setApprovalFlow(request.approvalFlow);
- return;
- }
-
- // Load from real API
+ // Always load from real API to get the latest data
if (request?.id || request?.requestId) {
const requestId = request.id || request.requestId;
try {
@@ -188,29 +184,61 @@ export function DealerClaimWorkflowTab({
const approvals = details?.approvalLevels || details?.approvals || [];
if (approvals && approvals.length > 0) {
// Transform approval levels to match expected format
- const flows = approvals.map((level: any) => ({
- step: level.levelNumber || level.level_number || 0,
- approver: level.approverName || level.approver_name || '',
- approverEmail: (level.approverEmail || level.approver_email || '').toLowerCase(),
- status: level.status?.toLowerCase() || 'waiting',
- tatHours: level.tatHours || level.tat_hours || 24,
- elapsedHours: level.elapsedHours || level.elapsed_hours,
- approvedAt: level.actionDate || level.action_date,
- comment: level.comments || level.comment,
- levelId: level.levelId || level.level_id,
- }));
- setApprovalFlow(flows);
+ // Include levelName and levelNumber for proper mapping
+ const flows = approvals
+ .map((level: any) => ({
+ step: level.levelNumber || level.level_number || 0,
+ levelNumber: level.levelNumber || level.level_number || 0,
+ levelName: level.levelName || level.level_name,
+ approver: level.approverName || level.approver_name || '',
+ approverEmail: (level.approverEmail || level.approver_email || '').toLowerCase(),
+ status: level.status?.toLowerCase() || 'waiting',
+ tatHours: level.tatHours || level.tat_hours || 24,
+ elapsedHours: level.elapsedHours || level.elapsed_hours,
+ approvedAt: level.actionDate || level.action_date,
+ comment: level.comments || level.comment,
+ levelId: level.levelId || level.level_id,
+ }))
+ // Sort by levelNumber to ensure correct order (critical for proper display)
+ .sort((a: any, b: any) => (a.levelNumber || 0) - (b.levelNumber || 0));
+
+ // Only update if the data actually changed (avoid unnecessary re-renders)
+ setApprovalFlow(prevFlows => {
+ // Check if flows are different
+ if (prevFlows.length !== flows.length) {
+ return flows;
+ }
+ // Check if any levelNumber or levelName changed
+ const hasChanges = prevFlows.some((prev: any, idx: number) => {
+ const curr = flows[idx];
+ return !curr ||
+ prev.levelNumber !== curr.levelNumber ||
+ prev.levelName !== curr.levelName ||
+ prev.approverEmail !== curr.approverEmail;
+ });
+ return hasChanges ? flows : prevFlows;
+ });
+ } else {
+ // If no approvals found, clear the flow
+ setApprovalFlow([]);
}
} catch (error) {
console.warn('Failed to load approval flows from API:', error);
+ // On error, try to use request.approvalFlow as fallback
+ if (request?.approvalFlow && request.approvalFlow.length > 0) {
+ setApprovalFlow(request.approvalFlow);
+ }
}
+ } else if (request?.approvalFlow && request.approvalFlow.length > 0) {
+ // Fallback: use request.approvalFlow only if no requestId available
+ setApprovalFlow(request.approvalFlow);
}
};
loadApprovalFlows();
- }, [request, refreshTrigger]);
+ }, [request?.id, request?.requestId, request?.totalLevels, refreshTrigger]);
- // Also reload when request.currentStep changes
+ // Also reload when request.currentStep or totalLevels changes (to catch step transitions and new approvers)
useEffect(() => {
if (request?.id || request?.requestId) {
const requestId = request.id || request.requestId;
@@ -219,17 +247,24 @@ export function DealerClaimWorkflowTab({
const details = await getWorkflowDetails(requestId);
const approvals = details?.approvalLevels || details?.approvals || [];
if (approvals && approvals.length > 0) {
- const flows = approvals.map((level: any) => ({
- step: level.levelNumber || level.level_number || 0,
- approver: level.approverName || level.approver_name || '',
- approverEmail: (level.approverEmail || level.approver_email || '').toLowerCase(),
- status: level.status?.toLowerCase() || 'waiting',
- tatHours: level.tatHours || level.tat_hours || 24,
- elapsedHours: level.elapsedHours || level.elapsed_hours,
- approvedAt: level.actionDate || level.action_date,
- comment: level.comments || level.comment,
- levelId: level.levelId || level.level_id,
- }));
+ const flows = approvals
+ .map((level: any) => ({
+ step: level.levelNumber || level.level_number || 0,
+ levelNumber: level.levelNumber || level.level_number || 0,
+ levelName: level.levelName || level.level_name,
+ approver: level.approverName || level.approver_name || '',
+ approverEmail: (level.approverEmail || level.approver_email || '').toLowerCase(),
+ status: level.status?.toLowerCase() || 'waiting',
+ tatHours: level.tatHours || level.tat_hours || 24,
+ elapsedHours: level.elapsedHours || level.elapsed_hours,
+ approvedAt: level.actionDate || level.action_date,
+ comment: level.comments || level.comment,
+ levelId: level.levelId || level.level_id,
+ }))
+ // Sort by levelNumber to ensure correct order
+ .sort((a: any, b: any) => (a.levelNumber || 0) - (b.levelNumber || 0));
+
+ // Update state with new flows
setApprovalFlow(flows);
}
} catch (error) {
@@ -238,7 +273,7 @@ export function DealerClaimWorkflowTab({
};
loadApprovalFlows();
}
- }, [request?.currentStep]);
+ }, [request?.currentStep, request?.totalLevels]);
// Enhanced refresh handler that also reloads approval flows
const handleRefresh = () => {
@@ -246,36 +281,128 @@ export function DealerClaimWorkflowTab({
onRefresh?.();
};
+ // Step title and description mapping based on actual step number (not array index)
+ // This handles cases where approvers are added between steps
+ const getStepTitle = (stepNumber: number, levelName?: string, approverName?: string): string => {
+ // Use levelName from backend if available (most accurate)
+ // Check if it's an "Additional Approver" - this indicates a dynamically added approver
+ if (levelName && levelName.trim()) {
+ // If it starts with "Additional Approver", use it as-is (it's already formatted)
+ if (levelName.toLowerCase().includes('additional approver')) {
+ return levelName;
+ }
+ // Otherwise use the levelName from backend (preserved from original step)
+ return levelName;
+ }
+
+ // Fallback to mapping based on step number
+ const stepTitleMap: Record = {
+ 1: 'Dealer - Proposal Submission',
+ 2: 'Requestor Evaluation & Confirmation',
+ 3: 'Department Lead Approval',
+ 4: 'Activity Creation',
+ 5: 'Dealer - Completion Documents',
+ 6: 'Requestor - Claim Approval',
+ 7: 'E-Invoice Generation',
+ 8: 'Credit Note from SAP',
+ };
+
+ // If step number exists in map, use it
+ if (stepTitleMap[stepNumber]) {
+ return stepTitleMap[stepNumber];
+ }
+
+ // For dynamically added steps, create a title from approver name or generic
+ if (approverName && approverName !== 'Unknown' && approverName !== 'System') {
+ return `Additional Approver - ${approverName}`;
+ }
+
+ return `Additional Approver - Step ${stepNumber}`;
+ };
+
+ const getStepDescription = (stepNumber: number, levelName?: string, approverName?: string): string => {
+ // Check if this is an "Additional Approver" (dynamically added)
+ const isAdditionalApprover = levelName && levelName.toLowerCase().includes('additional approver');
+
+ // If this is an additional approver, use generic description
+ if (isAdditionalApprover) {
+ if (approverName && approverName !== 'Unknown' && approverName !== 'System') {
+ return `${approverName} will review and approve this request as an additional approver.`;
+ }
+ return `Additional approver will review and approve this request.`;
+ }
+
+ // Use levelName to determine description (handles shifted steps correctly)
+ // This ensures descriptions shift with their steps when approvers are added
+ if (levelName && levelName.trim()) {
+ const levelNameLower = levelName.toLowerCase();
+
+ // Map level names to descriptions (works even after shifting)
+ if (levelNameLower.includes('dealer') && levelNameLower.includes('proposal')) {
+ return 'Dealer submits the proposal for the activity with comments including proposal document with requested details, cost break-up, timeline for closure, and other requests';
+ }
+ if (levelNameLower.includes('requestor') && (levelNameLower.includes('evaluation') || levelNameLower.includes('confirmation'))) {
+ return 'Requestor evaluates the request and confirms with comments. Decision point: Confirms? (YES → Continue to Dept Lead / NO → Request is cancelled)';
+ }
+ if (levelNameLower.includes('department lead')) {
+ return 'Department Lead approval. Decision point: Approved? (YES → Budget is blocked in the respective IO for the activity / NO → More clarification required → Request is cancelled)';
+ }
+ if (levelNameLower.includes('activity creation')) {
+ return 'Activity is created. Activity confirmation email is auto-triggered to dealer / requestor / Lead. IO confirmation to be made.';
+ }
+ if (levelNameLower.includes('dealer') && (levelNameLower.includes('completion') || levelNameLower.includes('documents'))) {
+ return 'Dealer submits the necessary documents upon completion of the activity including document attachments (Zip Folder) and brief description';
+ }
+ if (levelNameLower.includes('requestor') && (levelNameLower.includes('claim') || levelNameLower.includes('approval'))) {
+ return 'Requestor approves the claim in full or can modify the amount. If more information is required, can request additional details from dealer.';
+ }
+ if (levelNameLower.includes('e-invoice') || levelNameLower.includes('invoice generation')) {
+ return 'E-invoice will be generated through DMS.';
+ }
+ if (levelNameLower.includes('credit note') || levelNameLower.includes('sap')) {
+ return 'Got credit note from SAP. Review and send to dealer to complete the claim management process.';
+ }
+ }
+
+ // Fallback to step number mapping (for backwards compatibility)
+ const stepDescriptionMap: Record = {
+ 1: 'Dealer submits the proposal for the activity with comments including proposal document with requested details, cost break-up, timeline for closure, and other requests',
+ 2: 'Requestor evaluates the request and confirms with comments. Decision point: Confirms? (YES → Continue to Dept Lead / NO → Request is cancelled)',
+ 3: 'Department Lead approval. Decision point: Approved? (YES → Budget is blocked in the respective IO for the activity / NO → More clarification required → Request is cancelled)',
+ 4: 'Activity is created. Activity confirmation email is auto-triggered to dealer / requestor / Lead. IO confirmation to be made.',
+ 5: 'Dealer submits the necessary documents upon completion of the activity including document attachments (Zip Folder) and brief description',
+ 6: 'Requestor approves the claim in full or can modify the amount. If more information is required, can request additional details from dealer.',
+ 7: 'E-invoice will be generated through DMS.',
+ 8: 'Got credit note from SAP. Review and send to dealer to complete the claim management process.',
+ };
+
+ if (stepDescriptionMap[stepNumber]) {
+ return stepDescriptionMap[stepNumber];
+ }
+
+ // Final fallback
+ if (approverName && approverName !== 'Unknown' && approverName !== 'System') {
+ return `${approverName} will review and approve this request.`;
+ }
+
+ return `Step ${stepNumber} approval required.`;
+ };
+
// Transform approval flow to dealer claim workflow steps
const workflowSteps: WorkflowStep[] = approvalFlow.map((step: any, index: number) => {
- const stepTitles = [
- 'Dealer - Proposal Submission',
- 'Requestor Evaluation & Confirmation',
- 'Dept Lead Approval',
- 'Activity Creation',
- 'Dealer - Completion Documents',
- 'Requestor - Claim Approval',
- 'E-Invoice Generation',
- 'Credit Note from SAP',
- ];
-
- const stepDescriptions = [
- 'Dealer submits the proposal for the activity with comments including proposal document with requested details, cost break-up, timeline for closure, and other requests',
- 'Requestor evaluates the request and confirms with comments. Decision point: Confirms? (YES → Continue to Dept Lead / NO → Request is cancelled)',
- 'Department Lead approval. Decision point: Approved? (YES → Budget is blocked in the respective IO for the activity / NO → More clarification required → Request is cancelled)',
- 'Activity is created. Activity confirmation email is auto-triggered to dealer / requestor / Lead. IO confirmation to be made.',
- 'Dealer submits the necessary documents upon completion of the activity including document attachments (Zip Folder) and brief description',
- 'Requestor approves the claim in full or can modify the amount. If more information is required, can request additional details from dealer.',
- 'E-invoice will be generated through DMS.',
- 'Got credit note from SAP. Review and send to dealer to complete the claim management process.',
- ];
+ // Get actual step number from levelNumber or step field
+ const actualStepNumber = step.levelNumber || step.level_number || step.step || index + 1;
+
+ // Get levelName from the approval level if available
+ const levelName = step.levelName || step.level_name;
// Find approval data for this step
const approval = request?.approvals?.find((a: any) => a.levelId === step.levelId);
- // Extract IO details from internalOrder table (Step 3)
+ // Extract IO details from internalOrder table (Department Lead step - check by levelName)
let ioDetails = undefined;
- if (step.step === 3) {
+ const isDeptLeadStep = levelName && levelName.toLowerCase().includes('department lead');
+ if (isDeptLeadStep || actualStepNumber === 3) {
// Get IO details from dedicated internalOrder table
const internalOrder = request?.internalOrder || request?.internal_order;
@@ -314,7 +441,7 @@ export function DealerClaimWorkflowTab({
// Extract DMS details from approval data (Step 6)
let dmsDetails = undefined;
- if (step.step === 6) {
+ if (actualStepNumber === 6) {
if (approval?.dmsDetails) {
dmsDetails = {
dmsNumber: approval.dmsDetails.dmsNumber || '',
@@ -343,18 +470,18 @@ export function DealerClaimWorkflowTab({
// Waiting steps (future steps) should have elapsedHours = 0
// This ensures that when in step 1, only step 1 shows elapsed time, others show 0
const isWaiting = normalizedStatus === 'waiting';
- const isActive = normalizedStatus === 'pending' || normalizedStatus === 'in_progress';
- const isCompleted = normalizedStatus === 'approved' || normalizedStatus === 'rejected';
// Only calculate/show elapsed hours for active or completed steps
// For waiting steps, elapsedHours should be 0 (they haven't started yet)
const elapsedHours = isWaiting ? 0 : (step.elapsedHours || 0);
+ const approverName = step.approver || step.approverName || 'Unknown';
+
return {
- step: step.step || index + 1,
- title: stepTitles[index] || `Step ${step.step || index + 1}`,
- approver: step.approver || 'Unknown',
- description: stepDescriptions[index] || step.description || '',
+ step: actualStepNumber,
+ title: getStepTitle(actualStepNumber, levelName, approverName),
+ approver: approverName,
+ description: getStepDescription(actualStepNumber, levelName, approverName) || step.description || '',
tatHours: step.tatHours || 24,
status: normalizedStatus as any,
comment: step.comment || approval?.comment,
@@ -362,21 +489,33 @@ export function DealerClaimWorkflowTab({
elapsedHours, // Only non-zero for active/completed steps
ioDetails,
dmsDetails,
- einvoiceUrl: step.step === 7 ? (approval as any)?.einvoiceUrl : undefined,
- emailTemplateUrl: step.step === 4 ? (approval as any)?.emailTemplateUrl : undefined,
+ einvoiceUrl: actualStepNumber === 7 ? (approval as any)?.einvoiceUrl : undefined,
+ emailTemplateUrl: (approval as any)?.emailTemplateUrl || undefined,
};
});
const totalSteps = request?.totalSteps || 8;
// Calculate currentStep from approval flow - find the first pending or in_progress step
- // If no pending/in_progress step, use the request's currentStep
+ // IMPORTANT: Use the workflow's currentLevel from backend (most accurate)
+ // Fallback to finding first pending step if currentLevel not available
// Note: Status normalization already handled in workflowSteps mapping above
- const activeStep = workflowSteps.find(s => {
- const status = s.status?.toLowerCase() || '';
- return status === 'pending' || status === 'in_progress' || status === 'in-review' || status === 'in_review';
- });
- const currentStep = activeStep ? activeStep.step : (request?.currentStep || 1);
+ const backendCurrentLevel = request?.currentLevel || request?.current_level || request?.currentStep;
+
+ // Find the step that matches backend's currentLevel
+ const activeStepFromBackend = workflowSteps.find(s => s.step === backendCurrentLevel);
+
+ // If backend currentLevel exists and step is pending/in_progress, use it
+ // Otherwise, find first pending/in_progress step
+ const activeStep = activeStepFromBackend &&
+ (activeStepFromBackend.status === 'pending' || activeStepFromBackend.status === 'in_progress')
+ ? activeStepFromBackend
+ : workflowSteps.find(s => {
+ const status = s.status?.toLowerCase() || '';
+ return status === 'pending' || status === 'in_progress' || status === 'in-review' || status === 'in_review';
+ });
+
+ const currentStep = activeStep ? activeStep.step : (backendCurrentLevel || request?.currentStep || 1);
// Check if current user is the dealer (for steps 1 and 5)
const userEmail = (user as any)?.email?.toLowerCase() || '';
@@ -396,8 +535,30 @@ export function DealerClaimWorkflowTab({
const approverEmail = (currentApprovalLevel?.approverEmail || '').toLowerCase();
const isCurrentApprover = approverEmail && userEmail === approverEmail;
- // Check if user is approver for step 2 (requestor evaluation) - match by email
- const step2Level = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === 2);
+ // Find the initiator's step dynamically (Requestor Evaluation step)
+ // This handles cases where approvers are added between steps, causing step numbers to shift
+ const initiatorEmail = (
+ (request as any)?.initiator?.email?.toLowerCase() ||
+ (request as any)?.initiatorEmail?.toLowerCase() ||
+ ''
+ );
+
+ // Find the step where the initiator is the approver
+ // Check by: 1) approverEmail matches initiatorEmail, OR 2) levelName contains "Requestor Evaluation"
+ const initiatorStepLevel = approvalFlow.find((l: any) => {
+ const levelApproverEmail = (l.approverEmail || '').toLowerCase();
+ const levelName = (l.levelName || '').toLowerCase();
+ return (initiatorEmail && levelApproverEmail === initiatorEmail) ||
+ levelName.includes('requestor evaluation') ||
+ levelName.includes('requestor') && levelName.includes('confirmation');
+ });
+
+ const initiatorStepNumber = initiatorStepLevel
+ ? (initiatorStepLevel.step || initiatorStepLevel.levelNumber || initiatorStepLevel.level_number || 2)
+ : 2; // Fallback to 2 if not found
+
+ // Check if user is approver for the initiator's step (requestor evaluation)
+ const step2Level = initiatorStepLevel || approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === 2);
const step2ApproverEmail = (step2Level?.approverEmail || '').toLowerCase();
const isStep2Approver = step2ApproverEmail && userEmail === step2ApproverEmail;
@@ -406,9 +567,12 @@ export function DealerClaimWorkflowTab({
const step1ApproverEmail = (step1Level?.approverEmail || '').toLowerCase();
const isStep1Approver = step1ApproverEmail && userEmail === step1ApproverEmail;
- // Check if user is approver for step 3 (department lead approval) - match by email
- const step3Level = approvalFlow.find((l: any) => (l.step || l.levelNumber || l.level_number) === 3);
- const step3ApproverEmail = (step3Level?.approverEmail || '').toLowerCase();
+ // Find Department Lead step dynamically (handles step shifts)
+ const deptLeadStepLevel = approvalFlow.find((l: any) => {
+ const levelName = (l.levelName || '').toLowerCase();
+ return levelName.includes('department lead');
+ });
+ const step3ApproverEmail = (deptLeadStepLevel?.approverEmail || '').toLowerCase();
const isStep3Approver = step3ApproverEmail && userEmail === step3ApproverEmail;
// Handle proposal submission
@@ -469,20 +633,39 @@ export function DealerClaimWorkflowTab({
const requestId = request.id || request.requestId;
- // Get approval levels to find Step 2 levelId
+ // Get approval levels to find the initiator's step levelId dynamically
const details = await getWorkflowDetails(requestId);
const approvals = details?.approvalLevels || details?.approvals || [];
- const step2Level = approvals.find((level: any) =>
- (level.levelNumber || level.level_number) === 2
+
+ // Find the initiator's step by checking approverEmail or levelName
+ const initiatorEmail = (
+ (request as any)?.initiator?.email?.toLowerCase() ||
+ (request as any)?.initiatorEmail?.toLowerCase() ||
+ ''
);
+
+ const step2Level = approvals.find((level: any) => {
+ const levelApproverEmail = (level.approverEmail || level.approver_email || '').toLowerCase();
+ const levelName = (level.levelName || level.level_name || '').toLowerCase();
+ const levelNumber = level.levelNumber || level.level_number;
+
+ // Check if this is the initiator's step
+ return (initiatorEmail && levelApproverEmail === initiatorEmail) ||
+ levelName.includes('requestor evaluation') ||
+ (levelName.includes('requestor') && levelName.includes('confirmation')) ||
+ // Fallback: if initiatorStepNumber was found earlier, use it
+ (levelNumber === initiatorStepNumber);
+ }) || approvals.find((level: any) =>
+ (level.levelNumber || level.level_number) === 2
+ ); // Final fallback to level 2
if (!step2Level?.levelId && !step2Level?.level_id) {
- throw new Error('Step 2 approval level not found');
+ throw new Error('Initiator approval level not found');
}
const levelId = step2Level.levelId || step2Level.level_id;
- // Approve Step 2 using real API
+ // Approve the initiator's step using real API
await approveLevel(requestId, levelId, comments);
// Activity is logged by backend approval service - no need to create work note
@@ -505,20 +688,39 @@ export function DealerClaimWorkflowTab({
const requestId = request.id || request.requestId;
- // Get approval levels to find Step 2 levelId
+ // Get approval levels to find the initiator's step levelId dynamically
const details = await getWorkflowDetails(requestId);
const approvals = details?.approvalLevels || details?.approvals || [];
- const step2Level = approvals.find((level: any) =>
- (level.levelNumber || level.level_number) === 2
+
+ // Find the initiator's step by checking approverEmail or levelName
+ const initiatorEmail = (
+ (request as any)?.initiator?.email?.toLowerCase() ||
+ (request as any)?.initiatorEmail?.toLowerCase() ||
+ ''
);
+
+ const step2Level = approvals.find((level: any) => {
+ const levelApproverEmail = (level.approverEmail || level.approver_email || '').toLowerCase();
+ const levelName = (level.levelName || level.level_name || '').toLowerCase();
+ const levelNumber = level.levelNumber || level.level_number;
+
+ // Check if this is the initiator's step
+ return (initiatorEmail && levelApproverEmail === initiatorEmail) ||
+ levelName.includes('requestor evaluation') ||
+ (levelName.includes('requestor') && levelName.includes('confirmation')) ||
+ // Fallback: if initiatorStepNumber was found earlier, use it
+ (levelNumber === initiatorStepNumber);
+ }) || approvals.find((level: any) =>
+ (level.levelNumber || level.level_number) === 2
+ ); // Final fallback to level 2
if (!step2Level?.levelId && !step2Level?.level_id) {
- throw new Error('Step 2 approval level not found');
+ throw new Error('Initiator approval level not found');
}
const levelId = step2Level.levelId || step2Level.level_id;
- // Reject Step 2 using real API
+ // Reject the initiator's step using real API
await rejectLevel(requestId, levelId, 'Proposal rejected by requestor', comments);
// Activity is logged by backend approval service - no need to create work note
@@ -532,7 +734,7 @@ export function DealerClaimWorkflowTab({
}
};
- // Handle IO approval (Step 3)
+ // Handle IO approval (Department Lead step - found dynamically)
const handleIOApproval = async (data: {
ioNumber: string;
ioRemark: string;
@@ -545,15 +747,20 @@ export function DealerClaimWorkflowTab({
const requestId = request.id || request.requestId;
- // Get approval levels to find Step 3 levelId
+ // Get approval levels to find Department Lead step levelId dynamically
const details = await getWorkflowDetails(requestId);
const approvals = details?.approvalLevels || details?.approvals || [];
- const step3Level = approvals.find((level: any) =>
+
+ // Find Department Lead step by levelName (handles step shifts)
+ const step3Level = approvals.find((level: any) => {
+ const levelName = (level.levelName || level.level_name || '').toLowerCase();
+ return levelName.includes('department lead');
+ }) || approvals.find((level: any) =>
(level.levelNumber || level.level_number) === 3
- );
+ ); // Fallback to level 3
if (!step3Level?.levelId && !step3Level?.level_id) {
- throw new Error('Step 3 approval level not found');
+ throw new Error('Department Lead approval level not found');
}
const levelId = step3Level.levelId || step3Level.level_id;
@@ -675,20 +882,25 @@ export function DealerClaimWorkflowTab({
const requestId = request.id || request.requestId;
- // Get approval levels to find Step 3 levelId
+ // Get approval levels to find Department Lead step levelId dynamically
const details = await getWorkflowDetails(requestId);
const approvals = details?.approvalLevels || details?.approvals || [];
- const step3Level = approvals.find((level: any) =>
+
+ // Find Department Lead step by levelName (handles step shifts)
+ const step3Level = approvals.find((level: any) => {
+ const levelName = (level.levelName || level.level_name || '').toLowerCase();
+ return levelName.includes('department lead');
+ }) || approvals.find((level: any) =>
(level.levelNumber || level.level_number) === 3
- );
+ ); // Fallback to level 3
if (!step3Level?.levelId && !step3Level?.level_id) {
- throw new Error('Step 3 approval level not found');
+ throw new Error('Department Lead approval level not found');
}
const levelId = step3Level.levelId || step3Level.level_id;
- // Reject Step 3 using real API
+ // Reject Department Lead step using real API
await rejectLevel(requestId, levelId, 'Dept Lead rejected - More clarification required', comments);
// Activity is logged by backend approval service - no need to create work note
@@ -826,8 +1038,16 @@ export function DealerClaimWorkflowTab({
{workflowSteps.map((step, index) => {
- // Step is active if it's pending or in_progress and matches currentStep
- const isActive = (step.status === 'pending' || step.status === 'in_progress') && step.step === currentStep;
+ // Step is active if:
+ // 1. It's pending or in_progress
+ // 2. AND it matches currentStep (from backend or calculated)
+ // 3. AND it's the actual current step (not a future step that happens to be pending)
+ const stepStatus = step.status?.toLowerCase() || '';
+ const isPendingOrInProgress = stepStatus === 'pending' || stepStatus === 'in_progress';
+ const matchesCurrentStep = step.step === currentStep;
+
+ // Step is active only if it matches the current step AND is pending/in_progress
+ const isActive = isPendingOrInProgress && matchesCurrentStep;
const isCompleted = step.status === 'approved';
// Find approval data for this step to get SLA information
@@ -865,8 +1085,8 @@ export function DealerClaimWorkflowTab({
{step.status.toLowerCase()}
- {/* Email Template Button (Step 4) - Show when approved */}
- {step.step === 4 && step.status === 'approved' && (
+ {/* Email Template Button - Show when step has emailTemplateUrl and is approved */}
+ {step.emailTemplateUrl && step.status === 'approved' && (
))}
@@ -498,16 +580,40 @@ export function DealerCompletionDocumentsModal({
+ {canPreviewFile(attendanceSheet) && (
+ handlePreviewFile(attendanceSheet)}
+ title="Preview file"
+ >
+
+
+ )}
+ handleDownloadFile(attendanceSheet)}
+ title="Download file"
+ >
+
+
+ {
+ setAttendanceSheet(null);
+ if (attendanceInputRef.current) attendanceInputRef.current.value = '';
+ }}
+ title="Remove document"
+ >
+
+
+
)}
@@ -728,6 +882,96 @@ export function DealerCompletionDocumentsModal({