Replace src folder with new version
This commit is contained in:
809
src/components/pages/ProgrammeAssignment.tsx
Normal file
809
src/components/pages/ProgrammeAssignment.tsx
Normal file
@@ -0,0 +1,809 @@
|
||||
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@2.0.3";
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Calendar,
|
||||
Users,
|
||||
FileUp,
|
||||
Download,
|
||||
AlertCircle,
|
||||
Clock,
|
||||
MapPin,
|
||||
User,
|
||||
Building2,
|
||||
CheckCircle
|
||||
} from 'lucide-react';
|
||||
import { klcMockData } from '../../data/mockData';
|
||||
|
||||
interface ProgrammeAssignmentProps {
|
||||
programmeId: string;
|
||||
onNavigate: (route: string) => void;
|
||||
onLogout: () => void;
|
||||
user: any;
|
||||
}
|
||||
|
||||
interface AssignmentData {
|
||||
type: 'programme';
|
||||
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 ProgrammeAssignment({ programmeId, onNavigate, onLogout, user }: ProgrammeAssignmentProps) {
|
||||
const [currentStep, setCurrentStep] = useState(1);
|
||||
const [assignmentData, setAssignmentData] = useState<AssignmentData>({
|
||||
type: 'programme',
|
||||
itemId: programmeId,
|
||||
scope: 'organization',
|
||||
hrContacts: [],
|
||||
startDate: '',
|
||||
endDate: '',
|
||||
maxParticipants: 25,
|
||||
flags: {
|
||||
allowAddFeedbackGiver: false,
|
||||
hrAccessReports: false,
|
||||
hrAccessCertificate: false,
|
||||
onlineProgram: false,
|
||||
blockSystemEmails: false,
|
||||
},
|
||||
participantsSource: 'directory',
|
||||
participants: [],
|
||||
});
|
||||
|
||||
// Get programme data
|
||||
const programme = klcMockData.programmes?.find(p => p.id === programmeId);
|
||||
|
||||
// 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 programmes
|
||||
onNavigate('/programmes');
|
||||
}
|
||||
};
|
||||
|
||||
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 programme has feedback components
|
||||
const hasFeedbackComponents = programme?.structure?.preAssessment?.some((item: any) =>
|
||||
item.type === 'Profiler' && item.title.toLowerCase().includes('360')
|
||||
) || programme?.structure?.finalAssessment?.some((item: any) =>
|
||||
item.type === 'Profiler' && item.title.toLowerCase().includes('360')
|
||||
);
|
||||
|
||||
const breadcrumbItems = [
|
||||
{ label: "Admin", href: "/dashboard" },
|
||||
{ label: "Programmes", href: "/programmes" },
|
||||
{ label: programme?.title || "Programme", href: `/programmes/${programmeId}` },
|
||||
{ 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">Programme Summary</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div>
|
||||
<Label className="text-sm font-medium text-muted-foreground">Title</Label>
|
||||
<p className="font-medium">{programme?.title}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm font-medium text-muted-foreground">ID/Code</Label>
|
||||
<p className="font-mono text-sm">{programme?.id}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm font-medium text-muted-foreground">Status</Label>
|
||||
<Badge variant={programme?.status === 'Published' ? 'default' : 'secondary'}>
|
||||
{programme?.status}
|
||||
</Badge>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm font-medium text-muted-foreground">Owner</Label>
|
||||
<p>{programme?.coordinator}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm font-medium text-muted-foreground">Duration</Label>
|
||||
<p>{programme?.duration}</p>
|
||||
</div>
|
||||
<div>
|
||||
<Label className="text-sm font-medium text-muted-foreground">Structure</Label>
|
||||
<p className="text-sm text-muted-foreground">
|
||||
{programme?.structure?.preAssessment?.length || 0} Pre-assessments,
|
||||
{programme?.structure?.preLearning?.length || 0} Pre-learning,
|
||||
{programme?.structure?.classroomSessions?.length || 0} Sessions,
|
||||
{programme?.structure?.postLearning?.length || 0} Post-learning
|
||||
</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 programme.
|
||||
</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">Programme Name</Label>
|
||||
<p className="font-medium">{programme?.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={`/programmes/${programmeId}/assign`}
|
||||
breadcrumbItems={breadcrumbItems}
|
||||
>
|
||||
<div className="space-y-6">
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-2xl font-medium">Assignment — Programme</h1>
|
||||
<p className="text-muted-foreground">
|
||||
Assign programme 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user