Compare commits
No commits in common. "7d3b6a9da267f2d76fff7ab939c4d235c182d7e6" and "c6bd5a19ef50d64dd20c67c853ad9b87d4191d72" have entirely different histories.
7d3b6a9da2
...
c6bd5a19ef
@ -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
|
||||
|
||||
@ -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);
|
||||
}
|
||||
};
|
||||
|
||||
@ -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
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@ -19,6 +19,8 @@ import {
|
||||
CheckCircle,
|
||||
XCircle,
|
||||
FileText,
|
||||
User,
|
||||
Calendar,
|
||||
MessageSquare,
|
||||
} from 'lucide-react';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
@ -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);
|
||||
@ -61,18 +59,13 @@ export function useCreateRequestHandlers({
|
||||
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,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user