807 lines
31 KiB
TypeScript
807 lines
31 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
||
import { AuthenticatedLayout } from '../layout/AuthenticatedLayout';
|
||
import { Card, CardContent, CardHeader, CardTitle } from '../ui/card';
|
||
import { Button } from '../ui/button';
|
||
import { Badge } from '../ui/badge';
|
||
import { Input } from '../ui/input';
|
||
import { Label } from '../ui/label';
|
||
import { Textarea } from '../ui/textarea';
|
||
import { Checkbox } from '../ui/checkbox';
|
||
import { RadioGroup, RadioGroupItem } from '../ui/radio-group';
|
||
import {
|
||
Select,
|
||
SelectContent,
|
||
SelectItem,
|
||
SelectTrigger,
|
||
SelectValue,
|
||
} from '../ui/select';
|
||
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
|
||
import { Progress } from '../ui/progress';
|
||
import { Separator } from '../ui/separator';
|
||
import { toast } from "sonner";
|
||
import {
|
||
ChevronLeft,
|
||
ChevronRight,
|
||
Calendar,
|
||
Users,
|
||
FileUp,
|
||
Download,
|
||
AlertCircle,
|
||
Clock,
|
||
MapPin,
|
||
User,
|
||
Building2,
|
||
CheckCircle
|
||
} from 'lucide-react';
|
||
import { klcMockData } from '../../data/mockData';
|
||
import { Route } from '../../types/routes';
|
||
|
||
interface CourseAssignmentProps {
|
||
courseId: string;
|
||
onNavigate: (route: Route) => void;
|
||
onLogout: () => void;
|
||
user: any;
|
||
}
|
||
|
||
interface AssignmentData {
|
||
type: 'course';
|
||
itemId: string;
|
||
scope: 'organization' | 'individual';
|
||
organizationId?: string;
|
||
userId?: string;
|
||
hrContacts: string[];
|
||
startDate: string;
|
||
endDate: string;
|
||
completionDate?: string;
|
||
maxParticipants: number;
|
||
integrationId?: string;
|
||
flags: {
|
||
allowAddFeedbackGiver: boolean;
|
||
hrAccessReports: boolean;
|
||
hrAccessCertificate: boolean;
|
||
onlineProgram: boolean;
|
||
blockSystemEmails: boolean;
|
||
};
|
||
participantsSource: 'directory' | 'upload';
|
||
participants: string[];
|
||
participantsFile?: File;
|
||
feedbackGiversFile?: File;
|
||
}
|
||
|
||
// Mock integration options
|
||
const integrationOptions = [
|
||
{ id: 'int_001', name: 'Microsoft Teams' },
|
||
{ id: 'int_002', name: 'Zoom Workplace' },
|
||
{ id: 'int_003', name: 'Google Workspace' },
|
||
{ id: 'int_004', name: 'Slack Enterprise' },
|
||
{ id: 'int_005', name: 'Custom API' }
|
||
];
|
||
|
||
export function CourseAssignment({ courseId, onNavigate, onLogout, user }: CourseAssignmentProps) {
|
||
const [currentStep, setCurrentStep] = useState(1);
|
||
const [assignmentData, setAssignmentData] = useState<AssignmentData>({
|
||
type: 'course',
|
||
itemId: courseId,
|
||
scope: 'organization',
|
||
hrContacts: [],
|
||
startDate: '',
|
||
endDate: '',
|
||
maxParticipants: 25,
|
||
flags: {
|
||
allowAddFeedbackGiver: false,
|
||
hrAccessReports: false,
|
||
hrAccessCertificate: false,
|
||
onlineProgram: false,
|
||
blockSystemEmails: false,
|
||
},
|
||
participantsSource: 'directory',
|
||
participants: [],
|
||
});
|
||
|
||
// Get course data
|
||
const course = klcMockData.courses?.find(c => c.id === courseId);
|
||
|
||
// Mock organizations and users data
|
||
const organizations = klcMockData.users?.organizations || [];
|
||
const [filteredLearners, setFilteredLearners] = useState<any[]>([]);
|
||
const [selectedParticipants, setSelectedParticipants] = useState<string[]>([]);
|
||
const [participantsTab, setParticipantsTab] = useState('directory');
|
||
const [hrContactInput, setHrContactInput] = useState('');
|
||
|
||
// Load learners when organization changes
|
||
useEffect(() => {
|
||
if (assignmentData.organizationId) {
|
||
// Mock learners for selected organization
|
||
const mockLearners = [
|
||
{ id: 'usr_001', name: 'Ravi Kumar', email: 'ravi.kumar@org.example', department: 'Technology' },
|
||
{ id: 'usr_002', name: 'Priya Sharma', email: 'priya.sharma@org.example', department: 'Marketing' },
|
||
{ id: 'usr_003', name: 'Amit Singh', email: 'amit.singh@org.example', department: 'Operations' },
|
||
{ id: 'usr_004', name: 'Sneha Patel', email: 'sneha.patel@org.example', department: 'HR' },
|
||
{ id: 'usr_005', name: 'Rajesh Kumar', email: 'rajesh.kumar@org.example', department: 'Finance' }
|
||
];
|
||
setFilteredLearners(mockLearners);
|
||
}
|
||
}, [assignmentData.organizationId]);
|
||
|
||
const updateAssignmentData = (updates: Partial<AssignmentData>) => {
|
||
setAssignmentData(prev => ({ ...prev, ...updates }));
|
||
};
|
||
|
||
const validateStep = (step: number): boolean => {
|
||
switch (step) {
|
||
case 1:
|
||
if (assignmentData.scope === 'organization' && !assignmentData.organizationId) {
|
||
toast.error("Please select an organization");
|
||
return false;
|
||
}
|
||
if (assignmentData.scope === 'individual' && !assignmentData.userId) {
|
||
toast.error("Please select a user");
|
||
return false;
|
||
}
|
||
if (assignmentData.hrContacts.length === 0) {
|
||
toast.error("Please enter at least one HR contact");
|
||
return false;
|
||
}
|
||
if (assignmentData.maxParticipants <= 0) {
|
||
toast.error("Max participants must be greater than 0");
|
||
return false;
|
||
}
|
||
return true;
|
||
case 2:
|
||
if (!assignmentData.startDate) {
|
||
toast.error("Please select a start date");
|
||
return false;
|
||
}
|
||
if (!assignmentData.endDate) {
|
||
toast.error("Please select an end date");
|
||
return false;
|
||
}
|
||
if (new Date(assignmentData.endDate) < new Date(assignmentData.startDate)) {
|
||
toast.error("End date must be after start date");
|
||
return false;
|
||
}
|
||
if (assignmentData.completionDate && new Date(assignmentData.completionDate) < new Date(assignmentData.endDate)) {
|
||
toast.error("Completion date must be after end date");
|
||
return false;
|
||
}
|
||
return true;
|
||
case 3:
|
||
if (participantsTab === 'directory' && selectedParticipants.length === 0) {
|
||
toast.error("Please select at least one participant");
|
||
return false;
|
||
}
|
||
if (selectedParticipants.length > assignmentData.maxParticipants) {
|
||
toast.error(`Cannot exceed maximum of ${assignmentData.maxParticipants} participants`);
|
||
return false;
|
||
}
|
||
return true;
|
||
default:
|
||
return true;
|
||
}
|
||
};
|
||
|
||
const handleNext = () => {
|
||
if (validateStep(currentStep)) {
|
||
setCurrentStep(prev => Math.min(prev + 1, 4));
|
||
}
|
||
};
|
||
|
||
const handleBack = () => {
|
||
setCurrentStep(prev => Math.max(prev - 1, 1));
|
||
};
|
||
|
||
const handleAssign = () => {
|
||
if (validateStep(3)) {
|
||
toast.success("Assignment created.");
|
||
// Navigate to assignment details or back to courses
|
||
onNavigate('/courses');
|
||
}
|
||
};
|
||
|
||
const addHrContact = () => {
|
||
if (hrContactInput.trim()) {
|
||
updateAssignmentData({
|
||
hrContacts: [...assignmentData.hrContacts, hrContactInput.trim()]
|
||
});
|
||
setHrContactInput('');
|
||
}
|
||
};
|
||
|
||
const removeHrContact = (index: number) => {
|
||
const newContacts = assignmentData.hrContacts.filter((_, i) => i !== index);
|
||
updateAssignmentData({ hrContacts: newContacts });
|
||
};
|
||
|
||
const toggleParticipant = (participantId: string) => {
|
||
setSelectedParticipants(prev =>
|
||
prev.includes(participantId)
|
||
? prev.filter(id => id !== participantId)
|
||
: [...prev, participantId]
|
||
);
|
||
};
|
||
|
||
// Check if course has feedback components (profilers)
|
||
const hasFeedbackComponents = course?.assessments?.some((assessment: any) =>
|
||
assessment.type === 'Profiler' || assessment.title.toLowerCase().includes('360')
|
||
);
|
||
|
||
const breadcrumbItems = [
|
||
{ label: "Admin", href: "/dashboard" },
|
||
{ label: "Courses", href: "/courses" },
|
||
{ label: course?.title || "Course", href: `/courses/${courseId}` },
|
||
{ label: "Assign", current: true }
|
||
];
|
||
|
||
const renderStepIndicator = () => (
|
||
<div className="flex items-center justify-between mb-8">
|
||
{[1, 2, 3, 4].map((step) => (
|
||
<div key={step} className="flex items-center">
|
||
<div className={`
|
||
w-10 h-10 rounded-full flex items-center justify-center border-2 font-medium
|
||
${step <= currentStep
|
||
? 'bg-[var(--color-brand-primary)] text-white border-[var(--color-brand-primary)]'
|
||
: 'bg-background text-muted-foreground border-muted-foreground'}
|
||
`}>
|
||
{step < currentStep ? <CheckCircle className="h-5 w-5" /> : step}
|
||
</div>
|
||
{step < 4 && (
|
||
<div className={`
|
||
w-20 h-0.5 mx-2
|
||
${step < currentStep ? 'bg-[var(--color-brand-primary)]' : 'bg-muted-foreground'}
|
||
`} />
|
||
)}
|
||
</div>
|
||
))}
|
||
</div>
|
||
);
|
||
|
||
const renderSummaryCard = () => (
|
||
<Card className="w-80">
|
||
<CardHeader>
|
||
<CardTitle className="text-lg">Course Summary</CardTitle>
|
||
</CardHeader>
|
||
<CardContent className="space-y-4">
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">Title</Label>
|
||
<p className="font-medium">{course?.title}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">ID/Code</Label>
|
||
<p className="font-mono text-sm">{course?.id}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">Status</Label>
|
||
<Badge variant={course?.status === 'Published' ? 'default' : 'secondary'}>
|
||
{course?.status}
|
||
</Badge>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">Owner</Label>
|
||
<p>{course?.instructor}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">Duration</Label>
|
||
<p>{course?.duration}</p>
|
||
</div>
|
||
{course?.linkedIntegrations && course.linkedIntegrations.length > 0 && (
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">Linked Integrations</Label>
|
||
<p className="text-sm text-muted-foreground">
|
||
{course.linkedIntegrations.join(', ')}
|
||
</p>
|
||
</div>
|
||
)}
|
||
</CardContent>
|
||
</Card>
|
||
);
|
||
|
||
const renderStep1 = () => (
|
||
<div className="space-y-6">
|
||
<div>
|
||
<h3 className="text-lg font-medium mb-4">Who to assign</h3>
|
||
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<div className="space-y-4">
|
||
<div>
|
||
<Label htmlFor="assign-to" className="text-base font-medium">Assign to*</Label>
|
||
<RadioGroup
|
||
value={assignmentData.scope}
|
||
onValueChange={(value: 'organization' | 'individual') =>
|
||
updateAssignmentData({ scope: value, organizationId: undefined, userId: undefined })
|
||
}
|
||
className="mt-2"
|
||
>
|
||
<div className="flex items-center space-x-2">
|
||
<RadioGroupItem value="organization" id="org" />
|
||
<Label htmlFor="org">Organization</Label>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<RadioGroupItem value="individual" id="individual" />
|
||
<Label htmlFor="individual">Individual</Label>
|
||
</div>
|
||
</RadioGroup>
|
||
</div>
|
||
|
||
{assignmentData.scope === 'organization' ? (
|
||
<div>
|
||
<Label htmlFor="organization-select">Select Organisation*</Label>
|
||
<Select value={assignmentData.organizationId} onValueChange={(value) => updateAssignmentData({ organizationId: value })}>
|
||
<SelectTrigger className="min-h-[44px]">
|
||
<SelectValue placeholder="Choose organization..." />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{organizations.map((org: any) => (
|
||
<SelectItem key={org.id} value={org.id}>
|
||
{org.name}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
) : (
|
||
<div>
|
||
<Label htmlFor="user-select">Select User*</Label>
|
||
<Select value={assignmentData.userId} onValueChange={(value) => updateAssignmentData({ userId: value })}>
|
||
<SelectTrigger className="min-h-[44px]">
|
||
<SelectValue placeholder="Search and select user..." />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{filteredLearners.map((user: any) => (
|
||
<SelectItem key={user.id} value={user.id}>
|
||
{user.name} ({user.email})
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
)}
|
||
|
||
<div>
|
||
<Label htmlFor="hr-contacts">Enter HR Names*</Label>
|
||
<div className="space-y-2">
|
||
<div className="flex gap-2">
|
||
<Input
|
||
value={hrContactInput}
|
||
onChange={(e) => setHrContactInput(e.target.value)}
|
||
placeholder="Enter HR contact name"
|
||
className="min-h-[44px]"
|
||
onKeyPress={(e) => e.key === 'Enter' && addHrContact()}
|
||
/>
|
||
<Button type="button" onClick={addHrContact} className="min-h-[44px]">Add</Button>
|
||
</div>
|
||
<p className="text-sm text-muted-foreground">Primary, secondary HR contacts</p>
|
||
{assignmentData.hrContacts.length > 0 && (
|
||
<div className="flex flex-wrap gap-2">
|
||
{assignmentData.hrContacts.map((contact, index) => (
|
||
<Badge key={index} variant="secondary" className="px-2 py-1">
|
||
{contact}
|
||
<button
|
||
onClick={() => removeHrContact(index)}
|
||
className="ml-2 text-muted-foreground hover:text-foreground"
|
||
>
|
||
×
|
||
</button>
|
||
</Badge>
|
||
))}
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
<div>
|
||
<Label htmlFor="max-participants">Max. Number of Participants*</Label>
|
||
<Input
|
||
type="number"
|
||
value={assignmentData.maxParticipants}
|
||
onChange={(e) => updateAssignmentData({ maxParticipants: parseInt(e.target.value) || 0 })}
|
||
className="min-h-[44px]"
|
||
min="1"
|
||
/>
|
||
</div>
|
||
|
||
<div>
|
||
<Label htmlFor="integration">Select Integration</Label>
|
||
<Select value={assignmentData.integrationId} onValueChange={(value) => updateAssignmentData({ integrationId: value })}>
|
||
<SelectTrigger className="min-h-[44px]">
|
||
<SelectValue placeholder="Choose integration..." />
|
||
</SelectTrigger>
|
||
<SelectContent>
|
||
{integrationOptions.map((integration) => (
|
||
<SelectItem key={integration.id} value={integration.id}>
|
||
{integration.name}
|
||
</SelectItem>
|
||
))}
|
||
</SelectContent>
|
||
</Select>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-6">
|
||
<Label className="text-base font-medium">Permissions</Label>
|
||
<div className="grid grid-cols-2 gap-4 mt-3">
|
||
<div className="space-y-3">
|
||
<div className="flex items-center space-x-2">
|
||
<Checkbox
|
||
id="feedback-giver"
|
||
checked={assignmentData.flags.allowAddFeedbackGiver}
|
||
onCheckedChange={(checked) =>
|
||
updateAssignmentData({
|
||
flags: { ...assignmentData.flags, allowAddFeedbackGiver: !!checked }
|
||
})
|
||
}
|
||
/>
|
||
<Label htmlFor="feedback-giver" className="text-sm">Allow Participant to add Feedback Giver</Label>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<Checkbox
|
||
id="hr-reports"
|
||
checked={assignmentData.flags.hrAccessReports}
|
||
onCheckedChange={(checked) =>
|
||
updateAssignmentData({
|
||
flags: { ...assignmentData.flags, hrAccessReports: !!checked }
|
||
})
|
||
}
|
||
/>
|
||
<Label htmlFor="hr-reports" className="text-sm">Allow HR to Access Reports</Label>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<Checkbox
|
||
id="hr-certificate"
|
||
checked={assignmentData.flags.hrAccessCertificate}
|
||
onCheckedChange={(checked) =>
|
||
updateAssignmentData({
|
||
flags: { ...assignmentData.flags, hrAccessCertificate: !!checked }
|
||
})
|
||
}
|
||
/>
|
||
<Label htmlFor="hr-certificate" className="text-sm">Allow HR to Access Certificate</Label>
|
||
</div>
|
||
</div>
|
||
<div className="space-y-3">
|
||
<div className="flex items-center space-x-2">
|
||
<Checkbox
|
||
id="online-program"
|
||
checked={assignmentData.flags.onlineProgram}
|
||
onCheckedChange={(checked) =>
|
||
updateAssignmentData({
|
||
flags: { ...assignmentData.flags, onlineProgram: !!checked }
|
||
})
|
||
}
|
||
/>
|
||
<Label htmlFor="online-program" className="text-sm">Online Program</Label>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<Checkbox
|
||
id="block-emails"
|
||
checked={assignmentData.flags.blockSystemEmails}
|
||
onCheckedChange={(checked) =>
|
||
updateAssignmentData({
|
||
flags: { ...assignmentData.flags, blockSystemEmails: !!checked }
|
||
})
|
||
}
|
||
/>
|
||
<Label htmlFor="block-emails" className="text-sm">Block System Generated Emails</Label>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{assignmentData.flags.blockSystemEmails && (
|
||
<p className="text-sm text-muted-foreground mt-2">
|
||
No onboarding emails will be sent for this assignment.
|
||
</p>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
const renderStep2 = () => (
|
||
<div className="space-y-6">
|
||
<div>
|
||
<h3 className="text-lg font-medium mb-4">Schedule & Completion</h3>
|
||
|
||
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4 mb-6">
|
||
<div className="flex items-start gap-2">
|
||
<AlertCircle className="h-5 w-5 text-blue-600 mt-0.5" />
|
||
<p className="text-sm text-blue-800">
|
||
Dates apply to this assignment and are not set on the master course.
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="grid grid-cols-3 gap-6 max-w-2xl">
|
||
<div>
|
||
<Label htmlFor="start-date">Start Date*</Label>
|
||
<Input
|
||
type="date"
|
||
value={assignmentData.startDate}
|
||
onChange={(e) => updateAssignmentData({ startDate: e.target.value })}
|
||
className="min-h-[44px]"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Label htmlFor="end-date">End Date*</Label>
|
||
<Input
|
||
type="date"
|
||
value={assignmentData.endDate}
|
||
onChange={(e) => updateAssignmentData({ endDate: e.target.value })}
|
||
className="min-h-[44px]"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Label htmlFor="completion-date">Completion Date</Label>
|
||
<Input
|
||
type="date"
|
||
value={assignmentData.completionDate || ''}
|
||
onChange={(e) => updateAssignmentData({ completionDate: e.target.value })}
|
||
className="min-h-[44px]"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
const renderStep3 = () => (
|
||
<div className="space-y-6">
|
||
<div>
|
||
<h3 className="text-lg font-medium mb-4">Participants (and Feedback Givers)</h3>
|
||
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<div>
|
||
<Tabs value={participantsTab} onValueChange={setParticipantsTab}>
|
||
<TabsList className="grid w-full grid-cols-2">
|
||
<TabsTrigger value="directory">Add from Directory</TabsTrigger>
|
||
<TabsTrigger value="upload">Upload</TabsTrigger>
|
||
</TabsList>
|
||
|
||
<TabsContent value="directory" className="space-y-4">
|
||
{assignmentData.scope === 'organization' ? (
|
||
assignmentData.organizationId ? (
|
||
<div className="space-y-3">
|
||
<p className="text-sm text-muted-foreground">
|
||
Select participants from {organizations.find(o => o.id === assignmentData.organizationId)?.name}
|
||
</p>
|
||
{filteredLearners.map((learner) => (
|
||
<div key={learner.id} className="flex items-center space-x-2 p-2 border rounded">
|
||
<Checkbox
|
||
id={learner.id}
|
||
checked={selectedParticipants.includes(learner.id)}
|
||
onCheckedChange={() => toggleParticipant(learner.id)}
|
||
/>
|
||
<div className="flex-1">
|
||
<Label htmlFor={learner.id} className="font-medium">{learner.name}</Label>
|
||
<p className="text-sm text-muted-foreground">{learner.email} • {learner.department}</p>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
) : (
|
||
<p className="text-sm text-muted-foreground py-4">
|
||
Select an organization to browse learners.
|
||
</p>
|
||
)
|
||
) : (
|
||
<div className="py-4">
|
||
<p className="text-sm text-muted-foreground">Individual user will be automatically added as participant.</p>
|
||
{assignmentData.userId && (
|
||
<Badge variant="secondary" className="mt-2">
|
||
{filteredLearners.find(u => u.id === assignmentData.userId)?.name || 'Selected User'}
|
||
</Badge>
|
||
)}
|
||
</div>
|
||
)}
|
||
</TabsContent>
|
||
|
||
<TabsContent value="upload" className="space-y-4">
|
||
<Card className="border-dashed border-2">
|
||
<CardContent className="flex flex-col items-center justify-center py-6">
|
||
<FileUp className="h-8 w-8 text-muted-foreground mb-2" />
|
||
<Button variant="outline" className="mb-2">
|
||
Choose file
|
||
</Button>
|
||
<p className="text-sm text-muted-foreground">Upload participant list</p>
|
||
</CardContent>
|
||
</Card>
|
||
<Button variant="link" className="p-0 h-auto">
|
||
<Download className="h-4 w-4 mr-2" />
|
||
Download Participants Template
|
||
</Button>
|
||
</TabsContent>
|
||
</Tabs>
|
||
</div>
|
||
|
||
{hasFeedbackComponents && (
|
||
<div>
|
||
<Label className="text-base font-medium">Feedback Givers</Label>
|
||
<Card className="border-dashed border-2 mt-3">
|
||
<CardContent className="flex flex-col items-center justify-center py-6">
|
||
<FileUp className="h-8 w-8 text-muted-foreground mb-2" />
|
||
<Button variant="outline" className="mb-2">
|
||
Choose file
|
||
</Button>
|
||
<p className="text-sm text-muted-foreground">Upload feedback givers list</p>
|
||
</CardContent>
|
||
</Card>
|
||
<Button variant="link" className="p-0 h-auto mt-2">
|
||
<Download className="h-4 w-4 mr-2" />
|
||
Download Feedback Givers Template
|
||
</Button>
|
||
</div>
|
||
)}
|
||
</div>
|
||
|
||
<div className="flex items-center justify-between py-3 px-4 bg-muted/30 rounded-lg">
|
||
<span className="text-sm font-medium">
|
||
Participants selected: {selectedParticipants.length} (Max {assignmentData.maxParticipants})
|
||
</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
const renderStep4 = () => (
|
||
<div className="space-y-6">
|
||
<div>
|
||
<h3 className="text-lg font-medium mb-4">Review & Assign</h3>
|
||
|
||
<div className="space-y-6 max-w-4xl">
|
||
<div className="grid grid-cols-2 gap-6">
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">Course Name</Label>
|
||
<p className="font-medium">{course?.title}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">
|
||
{assignmentData.scope === 'organization' ? 'Select Organisation' : 'Select User'}
|
||
</Label>
|
||
<p className="font-medium">
|
||
{assignmentData.scope === 'organization'
|
||
? organizations.find(o => o.id === assignmentData.organizationId)?.name
|
||
: filteredLearners.find(u => u.id === assignmentData.userId)?.name
|
||
}
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">HR Names</Label>
|
||
<p className="font-medium">{assignmentData.hrContacts.join(', ')}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">Max. Number of Participants</Label>
|
||
<p className="font-medium">{assignmentData.maxParticipants}</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">Start Date • End Date</Label>
|
||
<p className="font-medium">
|
||
{assignmentData.startDate} • {assignmentData.endDate}
|
||
{assignmentData.completionDate && ` • ${assignmentData.completionDate}`}
|
||
</p>
|
||
</div>
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">Select Integration</Label>
|
||
<p className="font-medium">
|
||
{assignmentData.integrationId
|
||
? integrationOptions.find(i => i.id === assignmentData.integrationId)?.name
|
||
: 'None selected'
|
||
}
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<Separator />
|
||
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">Permissions</Label>
|
||
<div className="grid grid-cols-2 gap-2 mt-2">
|
||
<p className="text-sm">Allow Participant to add Feedback Giver: <span className="font-medium">{assignmentData.flags.allowAddFeedbackGiver ? 'Yes' : 'No'}</span></p>
|
||
<p className="text-sm">Allow HR to Access Reports: <span className="font-medium">{assignmentData.flags.hrAccessReports ? 'Yes' : 'No'}</span></p>
|
||
<p className="text-sm">Allow HR to Access Certificate: <span className="font-medium">{assignmentData.flags.hrAccessCertificate ? 'Yes' : 'No'}</span></p>
|
||
<p className="text-sm">Online Program: <span className="font-medium">{assignmentData.flags.onlineProgram ? 'Yes' : 'No'}</span></p>
|
||
<p className="text-sm">Block System Generated Emails: <span className="font-medium">{assignmentData.flags.blockSystemEmails ? 'Yes' : 'No'}</span></p>
|
||
</div>
|
||
</div>
|
||
|
||
<Separator />
|
||
|
||
<div>
|
||
<Label className="text-sm font-medium text-muted-foreground">Participants</Label>
|
||
<p className="font-medium">
|
||
{participantsTab === 'directory'
|
||
? `${selectedParticipants.length} selected from directory`
|
||
: 'Via upload'
|
||
}
|
||
</p>
|
||
{hasFeedbackComponents && (
|
||
<>
|
||
<Label className="text-sm font-medium text-muted-foreground mt-2 block">Feedback Givers</Label>
|
||
<p className="font-medium">Via upload</p>
|
||
</>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
);
|
||
|
||
const stepTitles = [
|
||
'Who to assign',
|
||
'Schedule & Completion',
|
||
'Participants (and Feedback Givers)',
|
||
'Review & Assign'
|
||
];
|
||
|
||
return (
|
||
<AuthenticatedLayout
|
||
user={user}
|
||
onLogout={onLogout}
|
||
currentRoute={`/courses/${courseId}/assign`}
|
||
breadcrumbItems={breadcrumbItems}
|
||
>
|
||
<div className="space-y-6">
|
||
<div className="flex items-center justify-between">
|
||
<div>
|
||
<h1 className="text-2xl font-medium">Assignment — Course</h1>
|
||
<p className="text-muted-foreground">
|
||
Assign course to organizations or individuals
|
||
</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="flex gap-6">
|
||
<div className="flex-1">
|
||
<Card>
|
||
<CardHeader>
|
||
<div className="flex items-center justify-between">
|
||
<CardTitle>{stepTitles[currentStep - 1]}</CardTitle>
|
||
<Badge variant="outline">Step {currentStep} of 4</Badge>
|
||
</div>
|
||
{renderStepIndicator()}
|
||
</CardHeader>
|
||
<CardContent>
|
||
{currentStep === 1 && renderStep1()}
|
||
{currentStep === 2 && renderStep2()}
|
||
{currentStep === 3 && renderStep3()}
|
||
{currentStep === 4 && renderStep4()}
|
||
</CardContent>
|
||
<div className="flex items-center justify-between px-6 py-4 border-t">
|
||
<Button
|
||
variant="outline"
|
||
onClick={handleBack}
|
||
disabled={currentStep === 1}
|
||
className="min-h-[44px]"
|
||
>
|
||
<ChevronLeft className="h-4 w-4 mr-2" />
|
||
Back
|
||
</Button>
|
||
<div className="flex gap-2">
|
||
{currentStep < 4 ? (
|
||
<Button
|
||
onClick={handleNext}
|
||
className="min-h-[44px]"
|
||
style={{ backgroundColor: "var(--color-brand-primary)" }}
|
||
>
|
||
Next
|
||
<ChevronRight className="h-4 w-4 ml-2" />
|
||
</Button>
|
||
) : (
|
||
<Button
|
||
onClick={handleAssign}
|
||
className="min-h-[44px]"
|
||
style={{ backgroundColor: "var(--color-brand-primary)" }}
|
||
>
|
||
Assign
|
||
</Button>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</Card>
|
||
</div>
|
||
|
||
{renderSummaryCard()}
|
||
</div>
|
||
</div>
|
||
</AuthenticatedLayout>
|
||
);
|
||
} |