diff --git a/src/components/dashboard/KPICard/KPICard.tsx b/src/components/dashboard/KPICard/KPICard.tsx index efa74e4..c8b17e0 100644 --- a/src/components/dashboard/KPICard/KPICard.tsx +++ b/src/components/dashboard/KPICard/KPICard.tsx @@ -27,41 +27,41 @@ export function KPICard({ }: KPICardProps) { return ( - + {title} -
+
- +
{value}
{subtitle && (
{subtitle}
)} {children && ( -
+
{children}
)} diff --git a/src/components/dashboard/StatCard/StatCard.tsx b/src/components/dashboard/StatCard/StatCard.tsx index bfe0fab..fbf2a2f 100644 --- a/src/components/dashboard/StatCard/StatCard.tsx +++ b/src/components/dashboard/StatCard/StatCard.tsx @@ -26,7 +26,7 @@ export function StatCard({ onClick={onClick} >

{label} diff --git a/src/components/workflow/CreateRequest/ApprovalWorkflowStep.tsx b/src/components/workflow/CreateRequest/ApprovalWorkflowStep.tsx new file mode 100644 index 0000000..1a9b20d --- /dev/null +++ b/src/components/workflow/CreateRequest/ApprovalWorkflowStep.tsx @@ -0,0 +1,359 @@ +import { motion } from 'framer-motion'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { Badge } from '@/components/ui/badge'; +import { Users, Settings, Shield, User, CheckCircle, AlertCircle, Info, Clock, Minus, Plus } from 'lucide-react'; +import { FormData } from '@/hooks/useCreateRequestForm'; +import { useMultiUserSearch } from '@/hooks/useUserSearch'; +import { useAuth } from '@/contexts/AuthContext'; +import { ensureUserExists } from '@/services/userApi'; + +interface ApprovalWorkflowStepProps { + formData: FormData; + updateFormData: (field: keyof FormData, value: any) => void; + onValidationError: (error: { type: string; email: string; message: string }) => void; +} + +/** + * Component: ApprovalWorkflowStep + * + * Purpose: Step 3 - Approval workflow configuration + * + * Features: + * - Configure number of approvers + * - Define approval hierarchy with TAT + * - User search with @ prefix + * - Test IDs for testing + * + * Note: This is a simplified version. Full implementation includes complex approver management. + */ +export function ApprovalWorkflowStep({ + formData, + updateFormData, + onValidationError +}: ApprovalWorkflowStepProps) { + const { user } = useAuth(); + const { userSearchResults, userSearchLoading, searchUsersForIndex, clearSearchForIndex } = useMultiUserSearch(); + + const handleApproverEmailChange = (index: number, value: string) => { + const newApprovers = [...formData.approvers]; + const previousEmail = newApprovers[index]?.email; + const emailChanged = previousEmail !== value; + + newApprovers[index] = { + ...newApprovers[index], + email: value, + level: index + 1, + userId: emailChanged ? undefined : newApprovers[index]?.userId, + name: emailChanged ? undefined : newApprovers[index]?.name, + department: emailChanged ? undefined : newApprovers[index]?.department, + avatar: emailChanged ? undefined : newApprovers[index]?.avatar + }; + updateFormData('approvers', newApprovers); + + if (!value || !value.startsWith('@') || value.length < 2) { + clearSearchForIndex(index); + return; + } + searchUsersForIndex(index, value, 10); + }; + + const handleUserSelect = async (index: number, selectedUser: any) => { + try { + const dbUser = await ensureUserExists({ + userId: selectedUser.userId, + email: selectedUser.email, + displayName: selectedUser.displayName, + firstName: selectedUser.firstName, + lastName: selectedUser.lastName, + department: selectedUser.department, + phone: selectedUser.phone, + mobilePhone: selectedUser.mobilePhone, + designation: selectedUser.designation, + jobTitle: selectedUser.jobTitle, + manager: selectedUser.manager, + employeeId: selectedUser.employeeId, + employeeNumber: selectedUser.employeeNumber, + secondEmail: selectedUser.secondEmail, + location: selectedUser.location + }); + + const updated = [...formData.approvers]; + updated[index] = { + ...updated[index], + email: selectedUser.email, + name: selectedUser.displayName || [selectedUser.firstName, selectedUser.lastName].filter(Boolean).join(' '), + userId: dbUser.userId, + level: index + 1, + }; + updateFormData('approvers', updated); + clearSearchForIndex(index); + } catch (err) { + console.error('Failed to ensure user exists:', err); + onValidationError({ + type: 'error', + email: selectedUser.email, + message: 'Failed to validate user. Please try again.' + }); + } + }; + + return ( + +

+
+ +
+

+ Approval Workflow +

+

+ Define the approval hierarchy and assign approvers by email ID. +

+
+ +
+ {/* Number of Approvers */} + + + + + Approval Configuration + + + Configure how many approvers you need and define the approval sequence. + + + +
+ +
+ + + {formData.approverCount || 1} + + +
+

+ Maximum 10 approvers allowed. Each approver will review sequentially. +

+
+
+
+ + {/* Approval Hierarchy */} + + + + + Approval Hierarchy * + + + Define the approval sequence. Each approver will review the request in order from Level 1 to Level {formData.approverCount || 1}. + + + + {/* Initiator Card */} +
+
+
+ +
+
+
+ Request Initiator + YOU +
+

Creates and submits the request

+
+
+
+ + {/* Dynamic Approver Cards */} + {Array.from({ length: formData.approverCount || 1 }, (_, index) => { + const level = index + 1; + const isLast = level === (formData.approverCount || 1); + + if (!formData.approvers[index]) { + const newApprovers = [...formData.approvers]; + newApprovers[index] = { email: '', name: '', level: level, tat: '' as any }; + updateFormData('approvers', newApprovers); + } + + return ( +
+
+
+
+ +
+
+
+ {level} +
+
+
+ + Approver Level {level} + + {isLast && ( + FINAL APPROVER + )} +
+
+
+
+ + {formData.approvers[index]?.email && formData.approvers[index]?.userId && ( + + + Verified + + )} +
+
+ handleApproverEmailChange(index, e.target.value)} + className="h-10 border-2 border-gray-300 focus:border-blue-500 mt-1 w-full" + data-testid={`approval-workflow-approver-${level}-email-input`} + /> + {/* Search suggestions dropdown */} + {(userSearchLoading[index] || (userSearchResults[index]?.length || 0) > 0) && ( +
+ {userSearchLoading[index] ? ( +
Searching...
+ ) : ( +
    + {userSearchResults[index]?.map((u) => ( +
  • handleUserSelect(index, u)} + data-testid={`approval-workflow-approver-${level}-search-result-${u.userId}`} + > +
    {u.displayName || u.email}
    +
    {u.email}
    +
  • + ))} +
+ )} +
+ )} +
+
+ +
+ +
+ { + const newApprovers = [...formData.approvers]; + newApprovers[index] = { + ...newApprovers[index], + tat: parseInt(e.target.value) || '', + level: level, + tatType: formData.approvers[index]?.tatType || 'hours' + }; + updateFormData('approvers', newApprovers); + }} + className="h-10 border-2 border-gray-300 focus:border-blue-500 flex-1" + data-testid={`approval-workflow-approver-${level}-tat-input`} + /> + +
+
+
+
+
+
+
+ ); + })} +
+
+
+ + ); +} + diff --git a/src/components/workflow/CreateRequest/BasicInformationStep.tsx b/src/components/workflow/CreateRequest/BasicInformationStep.tsx new file mode 100644 index 0000000..854fd3a --- /dev/null +++ b/src/components/workflow/CreateRequest/BasicInformationStep.tsx @@ -0,0 +1,225 @@ +import { motion } from 'framer-motion'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; +import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; +import { Badge } from '@/components/ui/badge'; +import { FileText, Zap, Clock } from 'lucide-react'; +import { FormData, RequestTemplate } from '@/hooks/useCreateRequestForm'; + +interface BasicInformationStepProps { + formData: FormData; + selectedTemplate: RequestTemplate | null; + updateFormData: (field: keyof FormData, value: any) => void; +} + +/** + * Component: BasicInformationStep + * + * Purpose: Step 2 - Basic information form (title, description, priority) + * + * Features: + * - Request title and description inputs + * - Priority selection (Express/Standard) + * - Template-specific additional fields + * - Test IDs for testing + */ +export function BasicInformationStep({ + formData, + selectedTemplate, + updateFormData +}: BasicInformationStepProps) { + return ( + +
+
+ +
+

+ Basic Information +

+

+ Provide the essential details for your {selectedTemplate?.name || 'request'}. +

+
+ +
+
+ +

+ Be specific and descriptive. This will be visible to all participants. +

+ updateFormData('title', e.target.value)} + className="text-base h-12 border-2 border-gray-300 focus:border-blue-500 bg-white shadow-sm" + data-testid="basic-information-title-input" + /> +
+ +
+ +

+ Explain what you need approval for, why it's needed, and any relevant background information. +

+