-
+
+
+
Overview
-
-
+
+
Workflow (8-Steps)
-
-
+
+
Documents
-
-
+
+
Activity
@@ -424,7 +424,7 @@ export function ClaimManagementDetail({
{/* Quick Actions */}
-
+
Quick Actions
@@ -468,7 +468,7 @@ export function ClaimManagementDetail({
{/* Spectators */}
{claim.spectators && claim.spectators.length > 0 && (
-
+
Spectators
diff --git a/src/pages/CreateRequest/CreateRequest.tsx b/src/pages/CreateRequest/CreateRequest.tsx
index a4c23c7..6242198 100644
--- a/src/pages/CreateRequest/CreateRequest.tsx
+++ b/src/pages/CreateRequest/CreateRequest.tsx
@@ -445,8 +445,37 @@ export function CreateRequest({ onBack, onSubmit, requestId: propRequestId, isEd
};
const addUser = (user: any, type: 'approvers' | 'spectators' | 'ccList' | 'invitedUsers') => {
+ const userEmail = (user.email || '').toLowerCase();
const currentList = formData[type];
- if (!currentList.find((u: any) => u.id === user.id)) {
+
+ // Check if user is already in the target list
+ if (currentList.find((u: any) => u.id === user.id || (u.email || '').toLowerCase() === userEmail)) {
+ alert(`${user.name || user.email} is already added as ${type.slice(0, -1)}.`);
+ return;
+ }
+
+ // Prevent adding same user in different roles
+ if (type === 'spectators') {
+ // Check if user is already an approver
+ const isApprover = formData.approvers.find((a: any) =>
+ a.id === user.id || (a.email || '').toLowerCase() === userEmail
+ );
+ if (isApprover) {
+ alert(`${user.name || user.email} is already an approver and cannot be added as a spectator.`);
+ return;
+ }
+ } else if (type === 'approvers') {
+ // Check if user is already a spectator
+ const isSpectator = formData.spectators.find((s: any) =>
+ s.id === user.id || (s.email || '').toLowerCase() === userEmail
+ );
+ if (isSpectator) {
+ alert(`${user.name || user.email} is already a spectator and cannot be added as an approver.`);
+ return;
+ }
+ }
+
+ // Add user to the list
const updatedList = [...currentList, user];
updateFormData(type, updatedList);
@@ -454,7 +483,6 @@ export function CreateRequest({ onBack, onSubmit, requestId: propRequestId, isEd
if (type === 'approvers') {
const maxApproverLevel = Math.max(...updatedList.map((a: any) => a.level), 0);
updateFormData('maxLevel', maxApproverLevel);
- }
}
};
@@ -1430,13 +1458,24 @@ export function CreateRequest({ onBack, onSubmit, requestId: propRequestId, isEd
key={u.userId}
className="p-2 text-sm cursor-pointer hover:bg-gray-50"
onClick={() => {
- // Prevent adding an approver as spectator
- const approverIds = (formData.approvers || []).map((a: any) => a?.userId).filter(Boolean);
- const approverEmails = (formData.approvers || []).map((a: any) => a?.email?.toLowerCase?.()).filter(Boolean);
- if (approverIds.includes(u.userId) || approverEmails.includes((u.email || '').toLowerCase())) {
- alert('This user is already an approver and cannot be added as a spectator.');
+ // Check if user is already a spectator
+ const spectatorIds = (formData.spectators || []).map((s: any) => s?.id).filter(Boolean);
+ const spectatorEmails = (formData.spectators || []).map((s: any) => s?.email?.toLowerCase?.()).filter(Boolean);
+ if (spectatorIds.includes(u.userId) || spectatorEmails.includes((u.email || '').toLowerCase())) {
+ alert(`${u.displayName || u.email} is already a spectator and cannot be added as an approver.`);
return;
}
+
+ // Check if user is already another approver
+ const approverEmails = (formData.approvers || [])
+ .filter((_, i) => i !== index) // Exclude current position
+ .map((a: any) => a?.email?.toLowerCase?.())
+ .filter(Boolean);
+ if (approverEmails.includes((u.email || '').toLowerCase())) {
+ alert(`${u.displayName || u.email} is already an approver at another level.`);
+ return;
+ }
+
const updated = [...formData.approvers];
updated[index] = {
...updated[index],
@@ -1777,11 +1816,26 @@ export function CreateRequest({ onBack, onSubmit, requestId: propRequestId, isEd
key={u.userId}
className="p-2 text-sm cursor-pointer hover:bg-gray-50"
onClick={() => {
+ // Check if user is already an approver
+ const approverIds = (formData.approvers || []).map((a: any) => a?.userId).filter(Boolean);
+ const approverEmails = (formData.approvers || []).map((a: any) => a?.email?.toLowerCase?.()).filter(Boolean);
+ if (approverIds.includes(u.userId) || approverEmails.includes((u.email || '').toLowerCase())) {
+ alert(`${u.displayName || u.email} is already an approver and cannot be added as a spectator.`);
+ setEmailInput('');
+ setSpectatorSearchResults([]);
+ return;
+ }
+
// Add selected spectator directly with precise id/name/email
const spectator = {
id: u.userId,
name: u.displayName || [u.firstName, u.lastName].filter(Boolean).join(' ') || u.email.split('@')[0],
email: u.email,
+ avatar: (u.displayName || u.email).split(' ').map((s: string) => s[0]).join('').slice(0, 2).toUpperCase(),
+ role: 'Spectator',
+ department: u.department || '',
+ level: 1,
+ canClose: false
} as any;
addUser(spectator, 'spectators');
setEmailInput('');
diff --git a/src/pages/MyRequests/MyRequests.tsx b/src/pages/MyRequests/MyRequests.tsx
index 2856b48..5a1fd53 100644
--- a/src/pages/MyRequests/MyRequests.tsx
+++ b/src/pages/MyRequests/MyRequests.tsx
@@ -20,6 +20,7 @@ import {
} from 'lucide-react';
import { motion } from 'framer-motion';
import workflowApi from '@/services/workflowApi';
+import { formatDateShort } from '@/utils/dateFormatter';
interface MyRequestsProps {
onViewRequest: (requestId: string, requestTitle?: string) => void;
@@ -28,6 +29,40 @@ interface MyRequestsProps {
// Removed mock data; list renders API data only
+// Helper to calculate due date from created date and TAT hours
+const calculateDueDate = (createdAt: string, tatHours: number, priority: string): string => {
+ if (!createdAt || !tatHours) return '';
+
+ try {
+ const startDate = new Date(createdAt);
+
+ if (priority === 'express') {
+ // Express: Calendar days (includes weekends)
+ const dueDate = new Date(startDate);
+ dueDate.setHours(dueDate.getHours() + tatHours);
+ return dueDate.toISOString();
+ } else {
+ // Standard: Working days (8 hours per day, skip weekends)
+ let remainingHours = tatHours;
+ let currentDate = new Date(startDate);
+
+ while (remainingHours > 0) {
+ // Skip weekends (Saturday = 6, Sunday = 0)
+ if (currentDate.getDay() !== 0 && currentDate.getDay() !== 6) {
+ const hoursToAdd = Math.min(remainingHours, 8);
+ remainingHours -= hoursToAdd;
+ }
+ if (remainingHours > 0) {
+ currentDate.setDate(currentDate.getDate() + 1);
+ }
+ }
+ return currentDate.toISOString();
+ }
+ } catch (error) {
+ console.error('Error calculating due date:', error);
+ return '';
+ }
+};
const getPriorityConfig = (priority: string) => {
switch (priority) {
@@ -121,22 +156,31 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
// Convert API/dynamic requests to the format expected by this component
const sourceRequests = (apiRequests.length ? apiRequests : dynamicRequests);
- const convertedDynamicRequests = sourceRequests.map((req: any) => ({
- id: req.requestNumber || req.request_number || req.requestId || req.id || req.request_id, // Use requestNumber as primary identifier
- requestId: req.requestId || req.id || req.request_id, // Keep requestId for API calls if needed
- displayId: req.requestNumber || req.request_number || req.id,
- title: req.title,
- description: req.description,
- status: (req.status || '').toString().toLowerCase().replace('in_progress','in-review'),
- priority: (req.priority || '').toString().toLowerCase(),
- department: req.department,
- submittedDate: req.submittedAt || (req.createdAt ? new Date(req.createdAt).toISOString().split('T')[0] : undefined),
- currentApprover: req.currentApprover?.name || req.currentApprover?.email || '—',
- approverLevel: req.currentLevel && req.totalLevels ? `${req.currentLevel} of ${req.totalLevels}` : (req.currentStep && req.totalSteps ? `${req.currentStep} of ${req.totalSteps}` : '—'),
- dueDate: req.dueDate ? new Date(req.dueDate).toISOString().split('T')[0] : undefined,
- templateType: req.templateType,
- templateName: req.templateName
- }));
+ const convertedDynamicRequests = sourceRequests.map((req: any) => {
+ // Calculate due date
+ const totalTatHours = Number(req.totalTatHours || 0);
+ const createdAt = req.submittedAt || req.submitted_at || req.createdAt || req.created_at;
+ const priority = (req.priority || '').toString().toLowerCase();
+ const calculatedDueDate = calculateDueDate(createdAt, totalTatHours, priority);
+
+ return {
+ id: req.requestNumber || req.request_number || req.requestId || req.id || req.request_id, // Use requestNumber as primary identifier
+ requestId: req.requestId || req.id || req.request_id, // Keep requestId for API calls if needed
+ displayId: req.requestNumber || req.request_number || req.id,
+ title: req.title,
+ description: req.description,
+ status: (req.status || '').toString().toLowerCase().replace('in_progress','in-review'),
+ priority: priority,
+ department: req.department,
+ submittedDate: req.submittedAt || (req.createdAt ? new Date(req.createdAt).toISOString().split('T')[0] : undefined),
+ createdAt: createdAt,
+ currentApprover: req.currentApprover?.name || req.currentApprover?.email || '—',
+ approverLevel: req.currentLevel && req.totalLevels ? `${req.currentLevel} of ${req.totalLevels}` : (req.currentStep && req.totalSteps ? `${req.currentStep} of ${req.totalSteps}` : '—'),
+ dueDate: calculatedDueDate || (req.dueDate ? new Date(req.dueDate).toISOString().split('T')[0] : undefined),
+ templateType: req.templateType,
+ templateName: req.templateName
+ };
+ });
// Use only API/dynamic requests
const allRequests = convertedDynamicRequests;
@@ -378,7 +422,7 @@ export function MyRequests({ onViewRequest, dynamicRequests = [] }: MyRequestsPr
- Estimated completion:
+ Estimated completion: {request.dueDate ? formatDateShort(request.dueDate) : 'Not set'}