Compare commits

..

No commits in common. "7d3b6a9da267f2d76fff7ab939c4d235c182d7e6" and "c6bd5a19ef50d64dd20c67c853ad9b87d4191d72" have entirely different histories.

4 changed files with 29 additions and 143 deletions

View File

@ -1,4 +1,4 @@
import { useState, useEffect } from 'react';
import { useState } from 'react';
import { Dialog, DialogContent, DialogDescription, DialogTitle } from '../ui/dialog';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
import { Button } from '../ui/button';
@ -8,16 +8,14 @@ import {
Receipt,
Package,
ArrowRight,
ArrowLeft,
Clock,
CheckCircle,
Target,
X,
Sparkles,
Check,
AlertCircle
Check
} from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
import { TokenManager } from '../../utils/tokenManager';
interface TemplateSelectionModalProps {
open: boolean;
@ -41,8 +39,7 @@ const AVAILABLE_TEMPLATES = [
'Document verification',
'E-invoice generation',
'Credit note issuance'
],
disabled: false
]
},
{
id: 'vendor-payment',
@ -58,32 +55,14 @@ const AVAILABLE_TEMPLATES = [
'Invoice verification',
'Multi-level approvals',
'Payment scheduling'
],
disabled: true,
comingSoon: true
]
}
];
export function TemplateSelectionModal({ open, onClose, onSelectTemplate }: TemplateSelectionModalProps) {
const [selectedTemplate, setSelectedTemplate] = useState<string | null>(null);
const [isDealer, setIsDealer] = useState(false);
// Check if user is a Dealer
useEffect(() => {
const userData = TokenManager.getUserData();
setIsDealer(userData?.jobTitle === 'Dealer');
}, []);
const handleSelect = (templateId: string) => {
// Don't allow selection if user is a dealer
if (isDealer) {
return;
}
// Don't allow selection if template is disabled
const template = AVAILABLE_TEMPLATES.find(t => t.id === templateId);
if (template?.disabled) {
return;
}
setSelectedTemplate(templateId);
};
@ -105,13 +84,12 @@ export function TemplateSelectionModal({ open, onClose, onSelectTemplate }: Temp
Choose from pre-configured templates with predefined workflows and approval chains for faster processing.
</DialogDescription>
{/* Back arrow button - Top left */}
{/* Custom Close button */}
<button
onClick={onClose}
className="!flex absolute top-6 left-6 z-50 w-10 h-10 rounded-full bg-white shadow-lg hover:shadow-xl border border-gray-200 items-center justify-center transition-all hover:scale-110"
aria-label="Go back"
className="absolute top-6 right-6 z-50 w-10 h-10 rounded-full bg-white shadow-lg hover:shadow-xl border border-gray-200 flex items-center justify-center transition-all hover:scale-110"
>
<ArrowLeft className="w-5 h-5 text-gray-600" />
<X className="w-5 h-5 text-gray-600" />
</button>
{/* Full Screen Content Container */}
@ -139,7 +117,6 @@ export function TemplateSelectionModal({ open, onClose, onSelectTemplate }: Temp
{AVAILABLE_TEMPLATES.map((template, index) => {
const Icon = template.icon;
const isSelected = selectedTemplate === template.id;
const isDisabled = isDealer || template.disabled;
return (
<motion.div
@ -147,16 +124,14 @@ export function TemplateSelectionModal({ open, onClose, onSelectTemplate }: Temp
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: index * 0.1 }}
whileHover={isDisabled ? {} : { scale: 1.03 }}
whileTap={isDisabled ? {} : { scale: 0.98 }}
whileHover={{ scale: 1.03 }}
whileTap={{ scale: 0.98 }}
>
<Card
className={`h-full transition-all duration-300 border-2 ${
isDisabled
? 'opacity-50 cursor-not-allowed border-gray-200'
: isSelected
? 'cursor-pointer border-blue-500 shadow-xl bg-blue-50/50 ring-2 ring-blue-200'
: 'cursor-pointer border-gray-200 hover:border-blue-300 hover:shadow-lg'
className={`cursor-pointer h-full transition-all duration-300 border-2 ${
isSelected
? 'border-blue-500 shadow-xl bg-blue-50/50 ring-2 ring-blue-200'
: 'border-gray-200 hover:border-blue-300 hover:shadow-lg'
}`}
onClick={() => handleSelect(template.id)}
>
@ -182,22 +157,6 @@ export function TemplateSelectionModal({ open, onClose, onSelectTemplate }: Temp
<CardDescription className="text-sm leading-relaxed">
{template.description}
</CardDescription>
{isDealer && (
<div className="mt-3 flex items-start gap-2 p-2 bg-amber-50 border border-amber-200 rounded-lg">
<AlertCircle className="w-4 h-4 text-amber-600 flex-shrink-0 mt-0.5" />
<p className="text-xs text-amber-800">
Not accessible for Dealers
</p>
</div>
)}
{template.comingSoon && !isDealer && (
<div className="mt-3 flex items-start gap-2 p-2 bg-blue-50 border border-blue-200 rounded-lg">
<AlertCircle className="w-4 h-4 text-blue-600 flex-shrink-0 mt-0.5" />
<p className="text-xs text-blue-800 font-semibold">
Coming Soon
</p>
</div>
)}
</div>
</CardHeader>
<CardContent className="pt-0 space-y-4">
@ -260,12 +219,12 @@ export function TemplateSelectionModal({ open, onClose, onSelectTemplate }: Temp
</Button>
<Button
onClick={handleContinue}
disabled={!selectedTemplate || isDealer || AVAILABLE_TEMPLATES.find(t => t.id === selectedTemplate)?.disabled}
disabled={!selectedTemplate}
size="lg"
className={`gap-2 px-8 ${
selectedTemplate && !isDealer && !AVAILABLE_TEMPLATES.find(t => t.id === selectedTemplate)?.disabled
selectedTemplate
? 'bg-blue-600 hover:bg-blue-700'
: 'bg-gray-400 cursor-not-allowed'
: 'bg-gray-400'
}`}
>
Continue with Template

View File

@ -1,4 +1,4 @@
import { useState, useRef, useEffect } from 'react';
import { useState, useRef } from 'react';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
@ -25,7 +25,6 @@ import {
FileText,
Users,
XCircle,
Loader2,
} from 'lucide-react';
import { format } from 'date-fns';
import { toast } from 'sonner';
@ -80,17 +79,6 @@ export function ClaimManagementWizard({ onBack, onSubmit }: ClaimManagementWizar
const [dealerSearchLoading, setDealerSearchLoading] = useState(false);
const [dealerSearchInput, setDealerSearchInput] = useState('');
const dealerSearchTimer = useRef<any>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const submitTimeoutRef = useRef<NodeJS.Timeout | null>(null);
// Cleanup timeout on unmount
useEffect(() => {
return () => {
if (submitTimeoutRef.current) {
clearTimeout(submitTimeoutRef.current);
}
};
}, []);
const [formData, setFormData] = useState({
activityName: '',
@ -295,11 +283,6 @@ export function ClaimManagementWizard({ onBack, onSubmit }: ClaimManagementWizar
};
const handleSubmit = () => {
// Prevent multiple submissions
if (isSubmitting) {
return;
}
// Approvers are already using integer levels with proper shifting
// Just sort them and prepare for submission
const approvers = formData.approvers || [];
@ -360,44 +343,9 @@ export function ClaimManagementWizard({ onBack, onSubmit }: ClaimManagementWizar
approvers: finalApprovers
};
// Set submitting state to prevent multiple clicks
setIsSubmitting(true);
// Clear any existing timeout
if (submitTimeoutRef.current) {
clearTimeout(submitTimeoutRef.current);
}
// Set a timeout as a fallback to reset loading state (30 seconds)
// In most cases, the parent component will navigate away on success,
// but this prevents the button from being stuck in loading state if there's an error
submitTimeoutRef.current = setTimeout(() => {
setIsSubmitting(false);
submitTimeoutRef.current = null;
}, 30000);
// Don't show toast here - let the parent component handle success/error after API call
if (onSubmit) {
try {
onSubmit(claimData);
// Note: On success, the component will unmount when parent navigates away (timeout cleared in useEffect)
// On error, the timeout will reset the state after 30 seconds
} catch (error) {
// If onSubmit throws synchronously, reset state immediately
if (submitTimeoutRef.current) {
clearTimeout(submitTimeoutRef.current);
submitTimeoutRef.current = null;
}
setIsSubmitting(false);
console.error('Error submitting claim:', error);
}
} else {
// If no onSubmit handler, reset immediately
if (submitTimeoutRef.current) {
clearTimeout(submitTimeoutRef.current);
submitTimeoutRef.current = null;
}
setIsSubmitting(false);
onSubmit(claimData);
}
};
@ -1046,20 +994,11 @@ export function ClaimManagementWizard({ onBack, onSubmit }: ClaimManagementWizar
) : (
<Button
onClick={handleSubmit}
disabled={!isStepValid() || isSubmitting}
disabled={!isStepValid()}
className="gap-2 bg-green-600 hover:bg-green-700 w-full sm:w-auto order-1 sm:order-2"
>
{isSubmitting ? (
<>
<Loader2 className="w-4 h-4 animate-spin" />
Submitting...
</>
) : (
<>
<Check className="w-4 h-4" />
Submit Claim Request
</>
)}
<Check className="w-4 h-4" />
Submit Claim Request
</Button>
)}
</div>

View File

@ -19,6 +19,8 @@ import {
CheckCircle,
XCircle,
FileText,
User,
Calendar,
MessageSquare,
} from 'lucide-react';
import { toast } from 'sonner';

View File

@ -8,7 +8,6 @@
*/
import { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { RequestTemplate, FormData } from '@/hooks/useCreateRequestForm';
import { PreviewDocument } from '../types/createRequest.types';
import { getDocumentPreviewUrl } from '@/services/workflowApi';
@ -45,7 +44,6 @@ export function useCreateRequestHandlers({
openValidationModal,
onSubmit,
}: UseHandlersOptions) {
const navigate = useNavigate();
const [showTemplateModal, setShowTemplateModal] = useState(false);
const [previewDocument, setPreviewDocument] =
useState<PreviewDocument | null>(null);
@ -60,19 +58,14 @@ export function useCreateRequestHandlers({
const suggestedDate = new Date();
suggestedDate.setDate(suggestedDate.getDate() + template.suggestedSLA);
updateFormData('slaEndDate', suggestedDate);
// Note: For 'existing-template', the modal will open when Next is clicked (handled in nextStep)
if (template.id === 'existing-template') {
setShowTemplateModal(true);
}
};
const handleTemplateSelection = (templateId: string) => {
// Navigate directly to the template-specific route when template is selected from modal
if (templateId === 'claim-management') {
navigate('/claim-management');
} else if (templateId === 'vendor-payment') {
// Add vendor-payment route if it exists, otherwise fallback to onSubmit
navigate('/vendor-payment');
} else if (onSubmit) {
// Fallback to onSubmit for other template types
if (onSubmit) {
onSubmit({ templateType: templateId });
}
};
@ -81,12 +74,6 @@ export function useCreateRequestHandlers({
const nextStep = async () => {
if (!isStepValid()) return;
// On step 1, if "existing-template" is selected, open the template selection modal
if (currentStep === 1 && _selectedTemplate?.id === 'existing-template') {
setShowTemplateModal(true);
return;
}
if (window.innerWidth < 640) {
window.scrollTo({ top: 0, behavior: 'smooth' });
}
@ -155,7 +142,6 @@ export function useCreateRequestHandlers({
setPreviewDocument(null);
};
return {
showTemplateModal,
setShowTemplateModal,