diff --git a/src/api/API.ts b/src/api/API.ts
index 49a9ea7..03f2cb5 100644
--- a/src/api/API.ts
+++ b/src/api/API.ts
@@ -27,6 +27,7 @@ export const API = {
// Onboarding
submitApplication: (data: any) => client.post('/onboarding/apply', data),
getApplications: () => client.get('/onboarding/applications'),
+ shortlistApplications: (data: any) => client.post('/onboarding/applications/shortlist', data),
getApplicationById: (id: string) => client.get(`/onboarding/applications/${id}`),
getLatestQuestionnaire: () => client.get('/questionnaire/latest'),
createQuestionnaireVersion: (data: any) => client.post('/questionnaire/version', data),
diff --git a/src/components/admin/QuestionnaireBuilder.tsx b/src/components/admin/QuestionnaireBuilder.tsx
index ff67dd2..ef21d0a 100644
--- a/src/components/admin/QuestionnaireBuilder.tsx
+++ b/src/components/admin/QuestionnaireBuilder.tsx
@@ -8,8 +8,8 @@ interface Question {
id?: string;
sectionName: string;
questionText: string;
- inputType: 'text' | 'yesno' | 'file' | 'number';
- options?: any;
+ inputType: 'text' | 'yesno' | 'file' | 'number' | 'select';
+ options?: { text: string; score: number }[];
weight: number;
order: number;
isMandatory: boolean;
@@ -53,10 +53,21 @@ const QuestionnaireBuilder: React.FC = () => {
const data = response.data.data;
setVersion(`${data.version} (Copy)`); // Default to making a copy
if (data.questions && data.questions.length > 0) {
- setQuestions(data.questions.map((q: any) => ({
- ...q,
- weight: parseFloat(q.weight) // Ensure weight is number
- })));
+ setQuestions(data.questions.map((q: any) => {
+ let normalizedType = q.inputType?.toLowerCase().trim();
+ if (normalizedType === 'mcq') normalizedType = 'select';
+
+ // Fallback validity check
+ const validTypes = ['text', 'number', 'file', 'yesno', 'select'];
+ if (!validTypes.includes(normalizedType)) normalizedType = 'text';
+
+ return {
+ ...q,
+ inputType: normalizedType,
+ weight: parseFloat(q.weight), // Ensure weight is number
+ options: q.questionOptions?.map((opt: any) => ({ text: opt.optionText, score: opt.score })) || []
+ };
+ }));
}
} else {
toast.error('Failed to load questionnaire');
@@ -92,10 +103,47 @@ const QuestionnaireBuilder: React.FC = () => {
const updateQuestion = (index: number, field: keyof Question, value: any) => {
const newQuestions = [...questions];
+
+ // Auto-populate Yes/No options if switching to yesno
+ if (field === 'inputType' && value === 'yesno' && (!newQuestions[index].options || newQuestions[index].options?.length === 0)) {
+ newQuestions[index].options = [
+ { text: 'Yes', score: 5 },
+ { text: 'No', score: 0 }
+ ];
+ } else if (field === 'inputType' && value === 'select' && (!newQuestions[index].options)) {
+ newQuestions[index].options = [];
+ }
+
newQuestions[index] = { ...newQuestions[index], [field]: value };
setQuestions(newQuestions);
};
+ const addOption = (questionIndex: number) => {
+ const newQuestions = [...questions];
+ if (!newQuestions[questionIndex].options) newQuestions[questionIndex].options = [];
+ newQuestions[questionIndex].options!.push({ text: '', score: 0 });
+ setQuestions(newQuestions);
+ };
+
+ const updateOption = (questionIndex: number, optionIndex: number, field: 'text' | 'score', value: any) => {
+ const newQuestions = [...questions];
+ if (newQuestions[questionIndex].options) {
+ newQuestions[questionIndex].options![optionIndex] = {
+ ...newQuestions[questionIndex].options![optionIndex],
+ [field]: value
+ };
+ setQuestions(newQuestions);
+ }
+ };
+
+ const removeOption = (questionIndex: number, optionIndex: number) => {
+ const newQuestions = [...questions];
+ if (newQuestions[questionIndex].options) {
+ newQuestions[questionIndex].options = newQuestions[questionIndex].options!.filter((_, i) => i !== optionIndex);
+ setQuestions(newQuestions);
+ }
+ };
+
const handleSave = async () => {
if (questions.some(q => !q.questionText)) {
toast.error('All questions must have text');
@@ -185,59 +233,61 @@ const QuestionnaireBuilder: React.FC = () => {
-
-
-
- updateQuestion(index, 'questionText', e.target.value)}
- className="w-full border border-slate-300 p-2.5 rounded-lg focus:ring-2 focus:ring-amber-500 outline-none transition-shadow"
- placeholder="Enter your question here..."
- />
-
+
+
+
+
+ updateQuestion(index, 'questionText', e.target.value)}
+ className="w-full border border-slate-300 p-2.5 rounded-lg focus:ring-2 focus:ring-amber-500 outline-none transition-shadow"
+ placeholder="Enter your question here..."
+ />
+
-
-
-
-
+
+
+
+
-
-
-
-
+
+
+
+
-
-
-
-
-
updateQuestion(index, 'weight', parseFloat(e.target.value))}
- className="w-full border border-slate-300 p-2.5 rounded-lg focus:ring-2 focus:ring-amber-500 outline-none pl-3 pr-8"
- title="Weightage"
- />
-
%
+
+
- {/*
updateQuestion(index, 'isMandatory', !q.isMandatory)}
>
@@ -245,8 +295,53 @@ const QuestionnaireBuilder: React.FC = () => {
Req.
*/}
+
+
+ {/* Options Editor for Select/YesNo */}
+ {(q.inputType === 'select' || q.inputType === 'yesno') && (
+
+
+
+ {q.options?.map((opt, optIndex) => (
+
+ ))}
+
+
+
+ )}
-
+
);
};
diff --git a/src/components/applications/ApplicationDetails.tsx b/src/components/applications/ApplicationDetails.tsx
index af2db87..5ebbeab 100644
--- a/src/components/applications/ApplicationDetails.tsx
+++ b/src/components/applications/ApplicationDetails.tsx
@@ -4,6 +4,7 @@ import { mockApplications, mockAuditLogs, mockDocuments, mockWorkNotes, mockLeve
import { onboardingService } from '../../services/onboarding.service';
import { WorkNotesPage } from './WorkNotesPage';
import QuestionnaireForm from '../dealer/QuestionnaireForm';
+import QuestionnaireResponseView from './QuestionnaireResponseView';
import { Button } from '../ui/button';
import { Badge } from '../ui/badge';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '../ui/tabs';
@@ -118,7 +119,8 @@ export function ApplicationDetails() {
ownsBike: data.ownRoyalEnfield === 'yes',
pastExperience: data.experienceYears ? `${data.experienceYears} years` : (data.description || ''),
status: data.overallStatus as ApplicationStatus,
- questionnaireMarks: 0,
+ questionnaireMarks: data.score || data.questionnaireMarks || 0, // Read from score or correct field
+ questionnaireResponses: data.questionnaireResponses || [], // Map responses
rank: 0,
totalApplicantsAtLocation: 0,
submissionDate: data.createdAt,
@@ -769,17 +771,7 @@ export function ApplicationDetails() {
{/* Questionnaire Response Tab */}
-
-
-
-
Questionnaire Responses
-
- {application.questionnaireMarks !== undefined && (
-
Score: {application.questionnaireMarks}/100
- )}
-
-
-
+
{/* Progress Tab */}
diff --git a/src/components/applications/ApplicationsPage.tsx b/src/components/applications/ApplicationsPage.tsx
index c9b0ab5..9812c19 100644
--- a/src/components/applications/ApplicationsPage.tsx
+++ b/src/components/applications/ApplicationsPage.tsx
@@ -30,6 +30,8 @@ import {
import { Progress } from '../ui/progress';
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '../ui/dialog';
import { Label } from '../ui/label';
+import { useSelector } from 'react-redux';
+import { RootState } from '../../store';
interface ApplicationsPageProps {
onViewDetails: (id: string) => void;
@@ -37,6 +39,7 @@ interface ApplicationsPageProps {
}
export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsPageProps) {
+ const { user: currentUser } = useSelector((state: RootState) => state.auth);
const [searchQuery, setSearchQuery] = useState('');
const [locationFilter, setLocationFilter] = useState('all');
const [statusFilter, setStatusFilter] = useState(initialFilter || 'all');
@@ -44,6 +47,7 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP
const [selectedIds, setSelectedIds] = useState([]);
const [sortBy, setSortBy] = useState<'date'>('date');
const [showNewApplicationModal, setShowNewApplicationModal] = useState(false);
+ const [showMyAssignments, setShowMyAssignments] = useState(false);
// Real Data Integration
const [applications, setApplications] = useState([]);
@@ -77,7 +81,8 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP
rank: 0,
totalApplicantsAtLocation: 0,
submissionDate: app.createdAt,
- assignedUsers: [],
+ assignedUsers: [], // Keeping this for UI compatibility if needed
+ assignedTo: app.assignedTo, // Add this field for filtering
progress: app.progressPercentage || 0,
isShortlisted: true, // Show all for admin view
// Add other fields to match interface
@@ -117,7 +122,10 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP
const isShortlisted = app.isShortlisted === true; // Only show shortlisted applications
const notExcluded = !excludedApplicationIds.includes(app.id); // Exclude APP-005, 006, 007, 008
- return matchesSearch && matchesLocation && matchesStatus && isShortlisted && notExcluded;
+ // New Filter: My Assignments
+ const matchesAssignment = !showMyAssignments || ((app as any).assignedTo === currentUser?.id);
+
+ return matchesSearch && matchesLocation && matchesStatus && isShortlisted && notExcluded && matchesAssignment;
})
.sort((a, b) => {
if (sortBy === 'date') {
@@ -240,6 +248,16 @@ export function ApplicationsPage({ onViewDetails, initialFilter }: ApplicationsP
+ {/* My Assignments Filter */}
+
+ setShowMyAssignments(checked as boolean)}
+ />
+
+
+
{/* Sort By */}