299 lines
12 KiB
TypeScript
299 lines
12 KiB
TypeScript
import { useState, useEffect } from 'react';
|
|
import { Dialog, DialogContent, DialogDescription, DialogTitle } from '../ui/dialog';
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../ui/card';
|
|
import { Button } from '../ui/button';
|
|
import { Badge } from '../ui/badge';
|
|
import { Separator } from '../ui/separator';
|
|
import {
|
|
Receipt,
|
|
Package,
|
|
ArrowRight,
|
|
ArrowLeft,
|
|
Clock,
|
|
CheckCircle,
|
|
Target,
|
|
Sparkles,
|
|
Check,
|
|
AlertCircle
|
|
} from 'lucide-react';
|
|
import { motion, AnimatePresence } from 'framer-motion';
|
|
import { TokenManager } from '../../utils/tokenManager';
|
|
|
|
interface TemplateSelectionModalProps {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
onSelectTemplate: (templateId: string) => void;
|
|
}
|
|
|
|
const AVAILABLE_TEMPLATES = [
|
|
{
|
|
id: 'claim-management',
|
|
name: 'Claim Management',
|
|
description: 'End-to-end dealer claim processing workflow with automatic IO generation and budget blocking',
|
|
category: 'Dealer Operations',
|
|
icon: Receipt,
|
|
color: 'from-blue-500 to-indigo-600',
|
|
estimatedTime: '5-7 days',
|
|
steps: 7,
|
|
features: [
|
|
'Automatic IO confirmation',
|
|
'Budget blocking',
|
|
'Document verification',
|
|
'E-invoice generation',
|
|
'Credit note issuance'
|
|
],
|
|
disabled: false
|
|
},
|
|
{
|
|
id: 'vendor-payment',
|
|
name: 'Vendor Payment',
|
|
description: 'Streamlined vendor payment approval with PO validation and financial controls',
|
|
category: 'Finance',
|
|
icon: Package,
|
|
color: 'from-green-500 to-emerald-600',
|
|
estimatedTime: '3-5 days',
|
|
steps: 5,
|
|
features: [
|
|
'PO matching',
|
|
'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);
|
|
};
|
|
|
|
const handleContinue = () => {
|
|
if (selectedTemplate) {
|
|
onSelectTemplate(selectedTemplate);
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
return (
|
|
<Dialog open={open} onOpenChange={onClose}>
|
|
<DialogContent
|
|
className="!fixed !inset-0 !top-0 !left-0 !right-0 !bottom-0 !w-screen !h-screen !max-w-none !translate-x-0 !translate-y-0 p-0 gap-0 border-0 !rounded-none bg-gradient-to-br from-gray-50 to-white [&>button]:hidden !m-0"
|
|
>
|
|
{/* Accessibility - Hidden Title and Description */}
|
|
<DialogTitle className="sr-only">Select a Template</DialogTitle>
|
|
<DialogDescription className="sr-only">
|
|
Choose from pre-configured templates with predefined workflows and approval chains for faster processing.
|
|
</DialogDescription>
|
|
|
|
{/* Back arrow button - Top left */}
|
|
<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"
|
|
>
|
|
<ArrowLeft className="w-5 h-5 text-gray-600" />
|
|
</button>
|
|
|
|
{/* Full Screen Content Container */}
|
|
<div className="h-full overflow-y-auto">
|
|
<div className="min-h-full flex flex-col items-center justify-center px-6 py-12">
|
|
{/* Header Section */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
className="text-center mb-12 max-w-3xl"
|
|
>
|
|
<div className="w-20 h-20 bg-gradient-to-br from-blue-500 to-purple-600 rounded-2xl flex items-center justify-center mx-auto mb-6">
|
|
<Sparkles className="w-10 h-10 text-white" />
|
|
</div>
|
|
<h1 className="text-4xl lg:text-5xl font-bold text-gray-900 mb-4">
|
|
Choose Your Template
|
|
</h1>
|
|
<p className="text-lg text-gray-600">
|
|
Select from pre-configured templates with predefined workflows and approval chains for faster processing.
|
|
</p>
|
|
</motion.div>
|
|
|
|
{/* Template Cards Grid */}
|
|
<div className="w-full max-w-5xl grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
|
|
{AVAILABLE_TEMPLATES.map((template, index) => {
|
|
const Icon = template.icon;
|
|
const isSelected = selectedTemplate === template.id;
|
|
const isDisabled = isDealer || template.disabled;
|
|
|
|
return (
|
|
<motion.div
|
|
key={template.id}
|
|
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 }}
|
|
>
|
|
<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'
|
|
}`}
|
|
onClick={() => handleSelect(template.id)}
|
|
>
|
|
<CardHeader className="space-y-4 pb-4">
|
|
<div className="flex items-start justify-between">
|
|
<div className={`w-14 h-14 rounded-xl bg-gradient-to-br ${template.color} flex items-center justify-center shadow-md`}>
|
|
<Icon className="w-7 h-7 text-white" />
|
|
</div>
|
|
{isSelected && (
|
|
<motion.div
|
|
initial={{ scale: 0 }}
|
|
animate={{ scale: 1 }}
|
|
transition={{ type: "spring", stiffness: 500, damping: 15 }}
|
|
>
|
|
<div className="w-8 h-8 rounded-full bg-blue-600 flex items-center justify-center shadow-md">
|
|
<Check className="w-5 h-5 text-white" />
|
|
</div>
|
|
</motion.div>
|
|
)}
|
|
</div>
|
|
<div className="text-left">
|
|
<CardTitle className="text-xl mb-2">{template.name}</CardTitle>
|
|
<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">
|
|
<div className="flex items-center gap-2">
|
|
<Badge variant="secondary" className="text-xs">
|
|
{template.category}
|
|
</Badge>
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
<div className="grid grid-cols-2 gap-3 text-xs text-gray-500">
|
|
<div className="flex items-center gap-1.5">
|
|
<Clock className="w-3.5 h-3.5" />
|
|
<span>{template.estimatedTime}</span>
|
|
</div>
|
|
<div className="flex items-center gap-1.5">
|
|
<Target className="w-3.5 h-3.5" />
|
|
<span>{template.steps} steps</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="space-y-2 pt-2">
|
|
<p className="text-xs text-gray-500 font-semibold">Key Features:</p>
|
|
<div className="space-y-1.5">
|
|
{template.features.slice(0, 3).map((feature, idx) => (
|
|
<div key={idx} className="flex items-center gap-2 text-xs text-gray-600">
|
|
<CheckCircle className="w-3 h-3 text-green-600 flex-shrink-0" />
|
|
<span>{feature}</span>
|
|
</div>
|
|
))}
|
|
{template.features.length > 3 && (
|
|
<p className="text-xs text-blue-600 italic pl-5">
|
|
+{template.features.length - 3} more features
|
|
</p>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</motion.div>
|
|
);
|
|
})}
|
|
</div>
|
|
|
|
{/* Action Buttons */}
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 20 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
transition={{ delay: 0.3 }}
|
|
className="flex flex-col sm:flex-row justify-center gap-4 mt-4"
|
|
>
|
|
<Button
|
|
variant="outline"
|
|
onClick={onClose}
|
|
size="lg"
|
|
className="px-8"
|
|
>
|
|
Cancel
|
|
</Button>
|
|
<Button
|
|
onClick={handleContinue}
|
|
disabled={!selectedTemplate || isDealer || AVAILABLE_TEMPLATES.find(t => t.id === selectedTemplate)?.disabled}
|
|
size="lg"
|
|
className={`gap-2 px-8 ${
|
|
selectedTemplate && !isDealer && !AVAILABLE_TEMPLATES.find(t => t.id === selectedTemplate)?.disabled
|
|
? 'bg-blue-600 hover:bg-blue-700'
|
|
: 'bg-gray-400 cursor-not-allowed'
|
|
}`}
|
|
>
|
|
Continue with Template
|
|
<ArrowRight className="w-4 h-4" />
|
|
</Button>
|
|
</motion.div>
|
|
|
|
{/* Selected template indicator */}
|
|
<AnimatePresence>
|
|
{selectedTemplate && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: 10 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -10 }}
|
|
className="mt-6 text-center"
|
|
>
|
|
<p className="text-sm text-gray-600">
|
|
Selected: <span className="font-semibold text-blue-600">
|
|
{AVAILABLE_TEMPLATES.find(t => t.id === selectedTemplate)?.name}
|
|
</span>
|
|
</p>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</div>
|
|
</div>
|
|
</DialogContent>
|
|
</Dialog>
|
|
);
|
|
}
|