hirarchy made stable and flow strted checking end to end level 3 completed
This commit is contained in:
parent
d2228543b1
commit
e68f96a929
@ -47,6 +47,7 @@ export const API = {
|
|||||||
updateArchitectureStatus: (applicationId: string, status: string, remarks?: string) => client.post(`/onboarding/applications/${applicationId}/architecture-status`, { status, remarks }),
|
updateArchitectureStatus: (applicationId: string, status: string, remarks?: string) => client.post(`/onboarding/applications/${applicationId}/architecture-status`, { status, remarks }),
|
||||||
generateDealerCodes: (applicationId: string) => client.post(`/onboarding/applications/${applicationId}/generate-codes`),
|
generateDealerCodes: (applicationId: string) => client.post(`/onboarding/applications/${applicationId}/generate-codes`),
|
||||||
updateApplicationStatus: (id: string, data: any) => client.put(`/onboarding/applications/${id}/status`, data),
|
updateApplicationStatus: (id: string, data: any) => client.put(`/onboarding/applications/${id}/status`, data),
|
||||||
|
retriggerEvaluators: (id: string) => client.post(`/onboarding/applications/${id}/retrigger-evaluators`),
|
||||||
|
|
||||||
// Documents
|
// Documents
|
||||||
uploadDocument: (id: string, data: any) => client.post(`/onboarding/applications/${id}/documents`, data, {
|
uploadDocument: (id: string, data: any) => client.post(`/onboarding/applications/${id}/documents`, data, {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ interface Question {
|
|||||||
id?: string;
|
id?: string;
|
||||||
sectionName: string;
|
sectionName: string;
|
||||||
questionText: string;
|
questionText: string;
|
||||||
inputType: 'text' | 'yesno' | 'file' | 'number' | 'select';
|
inputType: 'text' | 'yesno' | 'file' | 'number' | 'select' | 'mcq' | 'radio' | 'textarea' | 'email';
|
||||||
options?: { text: string; score: number }[];
|
options?: { text: string; score: number }[];
|
||||||
weight: number;
|
weight: number;
|
||||||
order: number;
|
order: number;
|
||||||
@ -58,7 +58,7 @@ const QuestionnaireBuilder: React.FC = () => {
|
|||||||
if (normalizedType === 'mcq') normalizedType = 'select';
|
if (normalizedType === 'mcq') normalizedType = 'select';
|
||||||
|
|
||||||
// Fallback validity check
|
// Fallback validity check
|
||||||
const validTypes = ['text', 'number', 'file', 'yesno', 'select'];
|
const validTypes = ['text', 'number', 'file', 'yesno', 'select', 'radio', 'textarea', 'email', 'mcq'];
|
||||||
if (!validTypes.includes(normalizedType)) normalizedType = 'text';
|
if (!validTypes.includes(normalizedType)) normalizedType = 'text';
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -295,10 +295,13 @@ const QuestionnaireBuilder: React.FC = () => {
|
|||||||
className="w-full border border-slate-300 p-2.5 rounded-lg focus:ring-2 focus:ring-amber-500 outline-none bg-white"
|
className="w-full border border-slate-300 p-2.5 rounded-lg focus:ring-2 focus:ring-amber-500 outline-none bg-white"
|
||||||
>
|
>
|
||||||
<option value="text">Text Input</option>
|
<option value="text">Text Input</option>
|
||||||
|
<option value="email">Email Address</option>
|
||||||
|
<option value="textarea">Long Text (Textarea)</option>
|
||||||
<option value="number">Numeric</option>
|
<option value="number">Numeric</option>
|
||||||
<option value="file">File Upload</option>
|
<option value="file">File Upload</option>
|
||||||
<option value="yesno">Yes / No</option>
|
<option value="yesno">Yes / No</option>
|
||||||
<option value="select">Dropdown / Multi-Choice</option>
|
<option value="select">Multiple Choice (Dropdown)</option>
|
||||||
|
<option value="radio">Multiple Choice (Radio)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -308,8 +311,8 @@ const QuestionnaireBuilder: React.FC = () => {
|
|||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={q.weight}
|
value={isNaN(q.weight) ? 0 : q.weight}
|
||||||
onChange={(e) => updateQuestion(index, 'weight', parseFloat(e.target.value))}
|
onChange={(e) => updateQuestion(index, 'weight', parseFloat(e.target.value) || 0)}
|
||||||
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"
|
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"
|
title="Weightage"
|
||||||
/>
|
/>
|
||||||
@ -330,7 +333,7 @@ const QuestionnaireBuilder: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Options Editor for Select/YesNo */}
|
{/* Options Editor for Select/YesNo */}
|
||||||
{(q.inputType === 'select' || q.inputType === 'yesno') && (
|
{(q.inputType === 'select' || q.inputType === 'yesno' || q.inputType === 'radio' || q.inputType === 'mcq') && (
|
||||||
<div className="w-full mt-4 pl-4 md:pl-16 border-t border-slate-100 pt-4">
|
<div className="w-full mt-4 pl-4 md:pl-16 border-t border-slate-100 pt-4">
|
||||||
<label className="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2">
|
<label className="block text-xs font-semibold text-slate-500 uppercase tracking-wider mb-2">
|
||||||
Answer Options & Scores
|
Answer Options & Scores
|
||||||
@ -347,13 +350,13 @@ const QuestionnaireBuilder: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs text-slate-400 font-medium">Score:</span>
|
<span className="text-xs text-slate-400 font-medium">Score:</span>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
value={opt.score}
|
value={isNaN(opt.score) ? 0 : opt.score}
|
||||||
max={q.weight}
|
max={isNaN(q.weight) ? 0 : q.weight}
|
||||||
min={0}
|
min={0}
|
||||||
onChange={(e) => updateOption(index, optIndex, 'score', e.target.value)}
|
onChange={(e) => updateOption(index, optIndex, 'score', e.target.value)}
|
||||||
className={`w-20 border ${opt.score > q.weight ? 'border-red-500 text-red-600' : 'border-slate-300'} p-2 rounded-md text-sm focus:ring-1 focus:ring-amber-500 outline-none`}
|
className={`w-20 border ${(opt.score > q.weight) || isNaN(opt.score) ? 'border-red-500 text-red-600' : 'border-slate-300'} p-2 rounded-md text-sm focus:ring-1 focus:ring-amber-500 outline-none`}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
|
|||||||
@ -102,7 +102,7 @@ export function UserManagementPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formData.zoneId) {
|
if (formData.zoneId) {
|
||||||
masterService.getStates(formData.zoneId).then((res: any) => {
|
masterService.getStates(formData.zoneId).then((res: any) => {
|
||||||
if (res.success) setStates(normalizeList(res, 'states'));
|
if (res && res.success) setStates(normalizeList(res, 'states'));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setStates([]);
|
setStates([]);
|
||||||
@ -113,7 +113,7 @@ export function UserManagementPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formData.stateId) {
|
if (formData.stateId) {
|
||||||
masterService.getDistricts(formData.stateId).then((res: any) => {
|
masterService.getDistricts(formData.stateId).then((res: any) => {
|
||||||
if (res.success) setDistricts(normalizeList(res, 'districts'));
|
if (res && res.success) setDistricts(normalizeList(res, 'districts'));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setDistricts([]);
|
setDistricts([]);
|
||||||
@ -124,7 +124,7 @@ export function UserManagementPage() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (formData.districtId) {
|
if (formData.districtId) {
|
||||||
masterService.getAreas(formData.districtId).then((res: any) => {
|
masterService.getAreas(formData.districtId).then((res: any) => {
|
||||||
if (res.success) setAreas(normalizeList(res, 'areas'));
|
if (res && res.success) setAreas(normalizeList(res, 'areas'));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
setAreas([]);
|
setAreas([]);
|
||||||
|
|||||||
@ -42,6 +42,8 @@ import { Progress } from '../ui/progress';
|
|||||||
import { Textarea } from '../ui/textarea';
|
import { Textarea } from '../ui/textarea';
|
||||||
import { Input } from '../ui/input';
|
import { Input } from '../ui/input';
|
||||||
import { Label } from '../ui/label';
|
import { Label } from '../ui/label';
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from '../ui/alert';
|
||||||
|
import { AlertCircle, RefreshCw } from 'lucide-react';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogTrigger } from '../ui/dialog';
|
import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription, DialogTrigger } from '../ui/dialog';
|
||||||
import { ScrollArea } from '../ui/scroll-area';
|
import { ScrollArea } from '../ui/scroll-area';
|
||||||
import {
|
import {
|
||||||
@ -313,6 +315,7 @@ export function ApplicationDetails() {
|
|||||||
zoneId: data.zoneId,
|
zoneId: data.zoneId,
|
||||||
regionId: data.regionId,
|
regionId: data.regionId,
|
||||||
areaId: data.areaId,
|
areaId: data.areaId,
|
||||||
|
districtId: data.districtId,
|
||||||
};
|
};
|
||||||
setApplication(mappedApp);
|
setApplication(mappedApp);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -336,21 +339,10 @@ export function ApplicationDetails() {
|
|||||||
const resp = await eorService.getChecklist(applicationId);
|
const resp = await eorService.getChecklist(applicationId);
|
||||||
if (resp.success && resp.data) {
|
if (resp.success && resp.data) {
|
||||||
setEorData(resp.data);
|
setEorData(resp.data);
|
||||||
} else {
|
|
||||||
// Auto-create if not found
|
|
||||||
await eorService.createChecklist(applicationId);
|
|
||||||
const retry = await eorService.getChecklist(applicationId);
|
|
||||||
if (retry.success) setEorData(retry.data);
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('EOR not found, attempting auto-create...');
|
console.log('EOR checklist not found or not yet initiated.');
|
||||||
try {
|
setEorData(null);
|
||||||
await eorService.createChecklist(applicationId);
|
|
||||||
const retry = await eorService.getChecklist(applicationId);
|
|
||||||
if (retry.success) setEorData(retry.data);
|
|
||||||
} catch (createErr) {
|
|
||||||
console.error('Fetch/Create EOR error:', createErr);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -384,9 +376,11 @@ export function ApplicationDetails() {
|
|||||||
}
|
}
|
||||||
}, [applicationId]);
|
}, [applicationId]);
|
||||||
|
|
||||||
const [activeTab, setActiveTab] = useState('questionnaire');
|
const [activeTab, setActiveTab ] = useState('questionnaire');
|
||||||
const [showApproveModal, setShowApproveModal] = useState(false);
|
const [showApproveModal, setShowApproveModal] = useState(false);
|
||||||
const [showRejectModal, setShowRejectModal] = useState(false);
|
const [showRejectModal, setShowRejectModal] = useState(false);
|
||||||
|
const [rejectionReason, setRejectionReason] = useState('');
|
||||||
|
const [scheduledInterviewParticipants, setScheduledInterviewParticipants] = useState<any[]>([]);
|
||||||
|
|
||||||
const [showWorkNoteModal, setShowWorkNoteModal] = useState(false);
|
const [showWorkNoteModal, setShowWorkNoteModal] = useState(false);
|
||||||
const [showScheduleModal, setShowScheduleModal] = useState(false);
|
const [showScheduleModal, setShowScheduleModal] = useState(false);
|
||||||
@ -398,7 +392,6 @@ export function ApplicationDetails() {
|
|||||||
const [selectedStage, setSelectedStage] = useState<string | null>(null);
|
const [selectedStage, setSelectedStage] = useState<string | null>(null);
|
||||||
const [interviewMode, setInterviewMode] = useState('virtual');
|
const [interviewMode, setInterviewMode] = useState('virtual');
|
||||||
const [approvalRemark, setApprovalRemark] = useState('');
|
const [approvalRemark, setApprovalRemark] = useState('');
|
||||||
const [rejectionReason, setRejectionReason] = useState('');
|
|
||||||
const [workNote, setWorkNote] = useState('');
|
const [workNote, setWorkNote] = useState('');
|
||||||
const [expandedBranches, setExpandedBranches] = useState<{ [key: string]: boolean }>({
|
const [expandedBranches, setExpandedBranches] = useState<{ [key: string]: boolean }>({
|
||||||
'architectural-work': true,
|
'architectural-work': true,
|
||||||
@ -418,7 +411,6 @@ export function ApplicationDetails() {
|
|||||||
const [approvalFile, setApprovalFile] = useState<File | null>(null); // State for approval modal file
|
const [approvalFile, setApprovalFile] = useState<File | null>(null); // State for approval modal file
|
||||||
const [isUploading, setIsUploading] = useState(false);
|
const [isUploading, setIsUploading] = useState(false);
|
||||||
const [selectedInterviewerId, setSelectedInterviewerId] = useState<string>('');
|
const [selectedInterviewerId, setSelectedInterviewerId] = useState<string>('');
|
||||||
const [scheduledInterviewParticipants, setScheduledInterviewParticipants] = useState<any[]>([]);
|
|
||||||
const [interviews, setInterviews] = useState<any[]>([]);
|
const [interviews, setInterviews] = useState<any[]>([]);
|
||||||
const [isScheduling, setIsScheduling] = useState(false);
|
const [isScheduling, setIsScheduling] = useState(false);
|
||||||
const [showAssignArchitectureModal, setShowAssignArchitectureModal] = useState(false);
|
const [showAssignArchitectureModal, setShowAssignArchitectureModal] = useState(false);
|
||||||
@ -481,7 +473,7 @@ export function ApplicationDetails() {
|
|||||||
interviewId,
|
interviewId,
|
||||||
criteriaScores,
|
criteriaScores,
|
||||||
feedback: ktMatrixRemarks,
|
feedback: ktMatrixRemarks,
|
||||||
recommendation: calculateKTScore() // Or separate recommendation field
|
recommendation: null // No auto-decision
|
||||||
});
|
});
|
||||||
|
|
||||||
toast.success('KT Matrix submitted successfully');
|
toast.success('KT Matrix submitted successfully');
|
||||||
@ -490,7 +482,8 @@ export function ApplicationDetails() {
|
|||||||
// Reset form
|
// Reset form
|
||||||
setKtMatrixScores({});
|
setKtMatrixScores({});
|
||||||
setKtMatrixRemarks('');
|
setKtMatrixRemarks('');
|
||||||
await fetchInterviews(); // Silent refresh
|
await fetchInterviews();
|
||||||
|
await fetchApplication(); // Refresh application status and progress
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to submit KT Matrix');
|
toast.error('Failed to submit KT Matrix');
|
||||||
} finally {
|
} finally {
|
||||||
@ -506,8 +499,9 @@ export function ApplicationDetails() {
|
|||||||
keyStrengths: '',
|
keyStrengths: '',
|
||||||
areasOfConcern: '',
|
areasOfConcern: '',
|
||||||
additionalComments: '',
|
additionalComments: '',
|
||||||
recommendation: '',
|
overallScore: '',
|
||||||
overallScore: ''
|
interviewerName: currentUser?.name || '',
|
||||||
|
interviewDate: new Date().toISOString().split('T')[0]
|
||||||
});
|
});
|
||||||
const [isSubmittingLevel2, setIsSubmittingLevel2] = useState(false);
|
const [isSubmittingLevel2, setIsSubmittingLevel2] = useState(false);
|
||||||
|
|
||||||
@ -543,7 +537,6 @@ export function ApplicationDetails() {
|
|||||||
await onboardingService.submitLevel2Feedback({
|
await onboardingService.submitLevel2Feedback({
|
||||||
interviewId,
|
interviewId,
|
||||||
overallScore: Number(level2Feedback.overallScore),
|
overallScore: Number(level2Feedback.overallScore),
|
||||||
recommendation: level2Feedback.recommendation,
|
|
||||||
feedbackItems
|
feedbackItems
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -558,10 +551,12 @@ export function ApplicationDetails() {
|
|||||||
keyStrengths: '',
|
keyStrengths: '',
|
||||||
areasOfConcern: '',
|
areasOfConcern: '',
|
||||||
additionalComments: '',
|
additionalComments: '',
|
||||||
recommendation: '',
|
overallScore: '',
|
||||||
overallScore: ''
|
interviewerName: currentUser?.name || '',
|
||||||
|
interviewDate: new Date().toISOString().split('T')[0]
|
||||||
});
|
});
|
||||||
fetchInterviews(); // Refresh to show feedback
|
fetchInterviews(); // Refresh to show feedback
|
||||||
|
fetchApplication(); // Refresh application status
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to submit Level 2 Feedback');
|
toast.error('Failed to submit Level 2 Feedback');
|
||||||
} finally {
|
} finally {
|
||||||
@ -574,13 +569,14 @@ export function ApplicationDetails() {
|
|||||||
strategicVision: '',
|
strategicVision: '',
|
||||||
managementCapabilities: '',
|
managementCapabilities: '',
|
||||||
operationalUnderstanding: '',
|
operationalUnderstanding: '',
|
||||||
keyStrengths: '',
|
|
||||||
areasOfConcern: '',
|
|
||||||
brandAlignment: '',
|
brandAlignment: '',
|
||||||
executiveSummary: '',
|
executiveSummary: '',
|
||||||
|
keyStrengths: '',
|
||||||
|
areasOfConcern: '',
|
||||||
additionalComments: '',
|
additionalComments: '',
|
||||||
recommendation: '',
|
overallScore: '',
|
||||||
overallScore: ''
|
interviewerName: currentUser?.name || '',
|
||||||
|
interviewDate: new Date().toISOString().split('T')[0]
|
||||||
});
|
});
|
||||||
const [isSubmittingLevel3, setIsSubmittingLevel3] = useState(false);
|
const [isSubmittingLevel3, setIsSubmittingLevel3] = useState(false);
|
||||||
|
|
||||||
@ -621,7 +617,6 @@ export function ApplicationDetails() {
|
|||||||
await onboardingService.submitLevel2Feedback({
|
await onboardingService.submitLevel2Feedback({
|
||||||
interviewId,
|
interviewId,
|
||||||
overallScore: Number(level3Feedback.overallScore),
|
overallScore: Number(level3Feedback.overallScore),
|
||||||
recommendation: level3Feedback.recommendation,
|
|
||||||
feedbackItems
|
feedbackItems
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -633,15 +628,17 @@ export function ApplicationDetails() {
|
|||||||
strategicVision: '',
|
strategicVision: '',
|
||||||
managementCapabilities: '',
|
managementCapabilities: '',
|
||||||
operationalUnderstanding: '',
|
operationalUnderstanding: '',
|
||||||
keyStrengths: '',
|
|
||||||
areasOfConcern: '',
|
|
||||||
brandAlignment: '',
|
brandAlignment: '',
|
||||||
executiveSummary: '',
|
executiveSummary: '',
|
||||||
|
keyStrengths: '',
|
||||||
|
areasOfConcern: '',
|
||||||
additionalComments: '',
|
additionalComments: '',
|
||||||
recommendation: '',
|
overallScore: '',
|
||||||
overallScore: ''
|
interviewerName: currentUser?.name || '',
|
||||||
|
interviewDate: new Date().toISOString().split('T')[0]
|
||||||
});
|
});
|
||||||
fetchInterviews();
|
fetchInterviews();
|
||||||
|
fetchApplication();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to submit Level 3 Feedback');
|
toast.error('Failed to submit Level 3 Feedback');
|
||||||
} finally {
|
} finally {
|
||||||
@ -713,7 +710,7 @@ export function ApplicationDetails() {
|
|||||||
|
|
||||||
// Include location from the application
|
// Include location from the application
|
||||||
if (application) {
|
if (application) {
|
||||||
params.locationId = application.locationId || application.areaId || application.regionId || application.zoneId;
|
params.locationId = application.districtId || application.areaId || application.regionId || application.zoneId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -735,12 +732,25 @@ export function ApplicationDetails() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (showScheduleModal) {
|
if (showScheduleModal && application) {
|
||||||
fetchUsers(interviewType);
|
fetchUsers(interviewType);
|
||||||
} else {
|
|
||||||
|
// Auto-fill participants based on pre-assigned evaluators for this level
|
||||||
|
const levelNum = parseInt(interviewType.replace('level', '')) || 1;
|
||||||
|
const preAssigned = (application?.participants || [])
|
||||||
|
.filter((p: any) => p.metadata?.interviewLevel === levelNum || p.metadata?.interviewLevel === String(levelNum))
|
||||||
|
.map((p: any) => p.user)
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (preAssigned.length > 0) {
|
||||||
|
setScheduledInterviewParticipants(preAssigned);
|
||||||
|
} else {
|
||||||
|
setScheduledInterviewParticipants([]);
|
||||||
|
}
|
||||||
|
} else if (showScheduleModal && application) {
|
||||||
fetchUsers(); // Default fetch for other modals like Assign
|
fetchUsers(); // Default fetch for other modals like Assign
|
||||||
}
|
}
|
||||||
}, [showScheduleModal, interviewType]);
|
}, [showScheduleModal, interviewType, application?.participants]);
|
||||||
|
|
||||||
const handleScheduleInterview = async () => {
|
const handleScheduleInterview = async () => {
|
||||||
if (!interviewDate) {
|
if (!interviewDate) {
|
||||||
@ -754,17 +764,17 @@ export function ApplicationDetails() {
|
|||||||
applicationId: application?.id,
|
applicationId: application?.id,
|
||||||
level: interviewType,
|
level: interviewType,
|
||||||
scheduledAt: interviewDate,
|
scheduledAt: interviewDate,
|
||||||
type: interviewMode, // 'virtual' | 'physical'
|
type: interviewMode === 'virtual' ? 'Virtual Interview' : 'Physical Interview',
|
||||||
location: interviewMode === 'physical' ? location : meetingLink,
|
location: interviewMode === 'virtual' ? meetingLink : location,
|
||||||
participants: scheduledInterviewParticipants.map(p => p.id)
|
participants: scheduledInterviewParticipants.map(p => p.id)
|
||||||
};
|
};
|
||||||
|
|
||||||
await onboardingService.scheduleInterview(payload);
|
await onboardingService.scheduleInterview(payload);
|
||||||
toast.success('Interview scheduled successfully');
|
toast.success('Interview scheduled successfully');
|
||||||
setShowScheduleModal(false);
|
setShowScheduleModal(false);
|
||||||
// Refresh interviews
|
// Refresh interviews
|
||||||
fetchInterviews();
|
await fetchInterviews();
|
||||||
fetchApplication(); // Refresh application status
|
await fetchApplication(); // Refresh application status
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to schedule interview');
|
toast.error('Failed to schedule interview');
|
||||||
@ -842,7 +852,7 @@ export function ApplicationDetails() {
|
|||||||
{
|
{
|
||||||
id: 3,
|
id: 3,
|
||||||
name: 'Shortlist',
|
name: 'Shortlist',
|
||||||
status: ['Shortlisted', 'Level 1 Interview Pending', 'Level 1 Approved', 'Level 2 Interview Pending', 'Level 2 Approved', 'Level 2 Recommended', 'Level 3 Interview Pending', 'Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : 'pending',
|
status: ['Shortlisted', 'Level 1 Interview Pending', 'Level 1 Approved', 'Level 2 Interview Pending', 'Level 2 Approved', 'Level 2 Recommended', 'Level 3 Interview Pending', 'Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved', 'Rejected'].includes(application.status) ? 'completed' : 'pending',
|
||||||
date: '2025-10-04',
|
date: '2025-10-04',
|
||||||
description: 'Application shortlisted by DD',
|
description: 'Application shortlisted by DD',
|
||||||
documentsUploaded: 2
|
documentsUploaded: 2
|
||||||
@ -850,28 +860,34 @@ export function ApplicationDetails() {
|
|||||||
{
|
{
|
||||||
id: 4,
|
id: 4,
|
||||||
name: '1st Level Interview',
|
name: '1st Level Interview',
|
||||||
status: ['Level 1 Approved', 'Level 2 Interview Pending', 'Level 2 Approved', 'Level 2 Recommended', 'Level 3 Interview Pending', 'Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Level 1 Interview Pending' ? 'active' : 'pending',
|
status: ['Level 1 Approved', 'Level 2 Interview Pending', 'Level 2 Approved', 'Level 2 Recommended', 'Level 3 Interview Pending', 'Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Level 1 Interview Pending' ? 'active' : application.status === 'Rejected' && application.progress >= 40 ? 'completed' : 'pending',
|
||||||
date: application.level1InterviewDate,
|
date: application.level1InterviewDate,
|
||||||
description: 'DD-ZM + RBM evaluation',
|
description: 'DD-ZM + RBM evaluation',
|
||||||
evaluators: ['DD-ZM', 'RBM'],
|
evaluators: (application.participants || [])
|
||||||
|
.filter((p: any) => p.metadata?.interviewLevel === 1 || (p.metadata?.interviewLevel === '1'))
|
||||||
|
.map((p: any) => `${p.user?.name} (${p.user?.role})`),
|
||||||
documentsUploaded: 1
|
documentsUploaded: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 5,
|
id: 5,
|
||||||
name: '2nd Level Interview',
|
name: '2nd Level Interview',
|
||||||
status: ['Level 2 Approved', 'Level 2 Recommended', 'Level 3 Interview Pending', 'Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : ['Level 2 Interview Pending'].includes(application.status) ? 'active' : 'pending',
|
status: ['Level 2 Approved', 'Level 2 Recommended', 'Level 3 Interview Pending', 'Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Level 2 Interview Pending' ? 'active' : application.status === 'Rejected' && application.progress >= 55 ? 'completed' : 'pending',
|
||||||
date: application.level2InterviewDate,
|
date: application.level2InterviewDate,
|
||||||
description: 'DD Lead + ZBH evaluation',
|
description: 'DD Lead + ZBH evaluation',
|
||||||
evaluators: ['DD Lead', 'ZBH'],
|
evaluators: (application.participants || [])
|
||||||
|
.filter((p: any) => p.metadata?.interviewLevel === 2 || (p.metadata?.interviewLevel === '2'))
|
||||||
|
.map((p: any) => `${p.user?.name} (${p.user?.role})`),
|
||||||
documentsUploaded: 1
|
documentsUploaded: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 6,
|
id: 6,
|
||||||
name: '3rd Level Interview',
|
name: '3rd Level Interview',
|
||||||
status: ['Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : ['Level 3 Interview Pending'].includes(application.status) ? 'active' : 'pending',
|
status: ['Level 3 Approved', 'FDD Verification', 'LOI In Progress', 'Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'Level 3 Interview Pending' ? 'active' : application.status === 'Rejected' && application.progress >= 70 ? 'completed' : 'pending',
|
||||||
date: application.level3InterviewDate,
|
date: application.level3InterviewDate,
|
||||||
description: 'NBH + DD Head evaluation',
|
description: 'NBH + DD Head evaluation',
|
||||||
evaluators: ['NBH', 'DD Head'],
|
evaluators: (application.participants || [])
|
||||||
|
.filter((p: any) => p.metadata?.interviewLevel === 3 || (p.metadata?.interviewLevel === '3'))
|
||||||
|
.map((p: any) => `${p.user?.name} (${p.user?.role})`),
|
||||||
documentsUploaded: 2
|
documentsUploaded: 2
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -888,6 +904,9 @@ export function ApplicationDetails() {
|
|||||||
status: ['Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'LOI In Progress' ? 'active' : 'pending',
|
status: ['Payment Pending', 'LOI Issued', 'Statutory LOI Ack', 'Dealer Code Generation', 'Architecture Team Assigned', 'Architecture Document Upload', 'Architecture Team Completion', 'LOA Pending', 'EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'LOI In Progress' ? 'active' : 'pending',
|
||||||
date: application.loiApprovalDate,
|
date: application.loiApprovalDate,
|
||||||
description: 'Letter of Intent approval',
|
description: 'Letter of Intent approval',
|
||||||
|
evaluators: (application.participants || [])
|
||||||
|
.filter((p: any) => p.metadata?.stageCode === 'LOI_APPROVAL')
|
||||||
|
.map((p: any) => `${p.user?.name} (${p.user?.role})`),
|
||||||
documentsUploaded: 1
|
documentsUploaded: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1036,6 +1055,9 @@ export function ApplicationDetails() {
|
|||||||
status: ['EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'LOA Pending' ? 'active' : 'pending',
|
status: ['EOR In Progress', 'EOR Complete', 'Inauguration', 'Approved'].includes(application.status) ? 'completed' : application.status === 'LOA Pending' ? 'active' : 'pending',
|
||||||
date: application.loaDate,
|
date: application.loaDate,
|
||||||
description: 'Letter of Authorization',
|
description: 'Letter of Authorization',
|
||||||
|
evaluators: (application.participants || [])
|
||||||
|
.filter((p: any) => p.metadata?.stageCode === 'LOA_APPROVAL')
|
||||||
|
.map((p: any) => `${p.user?.name} (${p.user?.role})`),
|
||||||
documentsUploaded: 1
|
documentsUploaded: 1
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -1153,10 +1175,7 @@ export function ApplicationDetails() {
|
|||||||
setApprovalFile(null); // Reset file
|
setApprovalFile(null); // Reset file
|
||||||
fetchInterviews();
|
fetchInterviews();
|
||||||
// Refresh application to check if status updated
|
// Refresh application to check if status updated
|
||||||
if (id) {
|
fetchApplication();
|
||||||
const appData = await onboardingService.getApplicationById(id);
|
|
||||||
setApplication(appData);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to approve interview');
|
toast.error('Failed to approve interview');
|
||||||
@ -1268,10 +1287,7 @@ export function ApplicationDetails() {
|
|||||||
setShowRejectModal(false);
|
setShowRejectModal(false);
|
||||||
setRejectionReason('');
|
setRejectionReason('');
|
||||||
fetchInterviews();
|
fetchInterviews();
|
||||||
if (id) {
|
fetchApplication();
|
||||||
const appData = await onboardingService.getApplicationById(id);
|
|
||||||
setApplication(appData);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error('Failed to reject interview');
|
toast.error('Failed to reject interview');
|
||||||
@ -1362,16 +1378,28 @@ export function ApplicationDetails() {
|
|||||||
requestId: applicationId,
|
requestId: applicationId,
|
||||||
requestType: 'application',
|
requestType: 'application',
|
||||||
userId: selectedUser,
|
userId: selectedUser,
|
||||||
participantType
|
participantType: 'contributor'
|
||||||
});
|
});
|
||||||
alert('User assigned successfully!');
|
toast.success('User assigned successfully!');
|
||||||
// Refresh application data
|
// Refresh application data
|
||||||
const data = await onboardingService.getApplicationById(applicationId);
|
fetchApplication();
|
||||||
setApplication({ ...application, participants: data.participants || [] });
|
|
||||||
setSelectedUser('');
|
setSelectedUser('');
|
||||||
setShowAssignModal(false);
|
setShowAssignModal(false);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
alert('Failed to assign user');
|
toast.error('Failed to assign user');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRetriggerEvaluators = async () => {
|
||||||
|
try {
|
||||||
|
setLoading(true);
|
||||||
|
await onboardingService.retriggerEvaluators(applicationId!);
|
||||||
|
toast.success('Evaluators re-assigned successfully');
|
||||||
|
await fetchApplication();
|
||||||
|
} catch (error) {
|
||||||
|
toast.error('Failed to re-assign evaluators');
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1401,6 +1429,11 @@ export function ApplicationDetails() {
|
|||||||
(e: any) => e.evaluatorId === currentUser?.id
|
(e: any) => e.evaluatorId === currentUser?.id
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Helper to check interview level completion
|
||||||
|
const isInterviewCompleted = (level: number) => {
|
||||||
|
return interviewsList.some(i => (Number(i.level) === level) && i.status === 'Completed');
|
||||||
|
};
|
||||||
|
|
||||||
// Robust checks for feedback and decision
|
// Robust checks for feedback and decision
|
||||||
// 1. If there's an active interview, feedback is required before Approve/Reject
|
// 1. If there's an active interview, feedback is required before Approve/Reject
|
||||||
// 2. hasMadeDecision should check if the evaluation has a recommendation
|
// 2. hasMadeDecision should check if the evaluation has a recommendation
|
||||||
@ -1673,11 +1706,50 @@ export function ApplicationDetails() {
|
|||||||
{stage.description && (
|
{stage.description && (
|
||||||
<p className="text-slate-600 text-sm mt-0.5">{stage.description}</p>
|
<p className="text-slate-600 text-sm mt-0.5">{stage.description}</p>
|
||||||
)}
|
)}
|
||||||
{stage.evaluators && (
|
{stage.evaluators && stage.evaluators.length > 0 ? (
|
||||||
<p className="text-amber-600 text-sm mt-0.5">
|
<p className="text-amber-600 text-sm mt-0.5">
|
||||||
Evaluators: {stage.evaluators.join(' + ')}
|
Evaluators: {stage.evaluators.join(' + ')}
|
||||||
</p>
|
</p>
|
||||||
)}
|
) : (() => {
|
||||||
|
// Determine expected count for this stage
|
||||||
|
const expectedMap: Record<number, number> = {
|
||||||
|
4: 2, // L1 Interview (ZM + RBM)
|
||||||
|
5: 2, // L2 Interview (ZBH + DD Lead)
|
||||||
|
6: 2, // L3 Interview (NBH + DD Head)
|
||||||
|
8: 3, // LOI Approval (Finance + DD Head + NBH)
|
||||||
|
12: 2 // LOA Approval (DD Head + NBH)
|
||||||
|
};
|
||||||
|
const stageId = Number(stage.id);
|
||||||
|
const expectedCount = expectedMap[stageId];
|
||||||
|
const actualCount = stage.evaluators?.length || 0;
|
||||||
|
|
||||||
|
if (expectedCount && actualCount < expectedCount && application.status !== 'Rejected') {
|
||||||
|
return (
|
||||||
|
<div className="mt-2">
|
||||||
|
<Alert variant="destructive" className="py-2 px-3 border-amber-200 bg-amber-50 text-amber-800">
|
||||||
|
<AlertCircle className="h-4 w-4 text-amber-600" />
|
||||||
|
<AlertTitle className="text-xs font-semibold">Missing Evaluators</AlertTitle>
|
||||||
|
<AlertDescription className="text-xs">
|
||||||
|
{actualCount === 0
|
||||||
|
? "Respective role users were not found for this location."
|
||||||
|
: `Some roles (${actualCount}/${expectedCount}) are missing for this location.`
|
||||||
|
}
|
||||||
|
<Button
|
||||||
|
variant="link"
|
||||||
|
size="sm"
|
||||||
|
className="h-auto p-0 ml-1 text-xs text-amber-700 underline"
|
||||||
|
onClick={handleRetriggerEvaluators}
|
||||||
|
>
|
||||||
|
<RefreshCw className="w-3 h-3 mr-1" />
|
||||||
|
Re-trigger Assignment
|
||||||
|
</Button>
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
})()}
|
||||||
{/* Stage Docs Link */}
|
{/* Stage Docs Link */}
|
||||||
{(() => {
|
{(() => {
|
||||||
const stageDocsCount = documents.filter(doc =>
|
const stageDocsCount = documents.filter(doc =>
|
||||||
@ -2344,7 +2416,8 @@ export function ApplicationDetails() {
|
|||||||
Work Note
|
Work Note
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{currentUser && ['DD Admin', 'Super Admin', 'DD AM', 'ASM'].includes(currentUser.role) && (
|
{currentUser && ['DD Admin', 'Super Admin', 'DD AM', 'ASM'].includes(currentUser.role) &&
|
||||||
|
!([1, 2, 3].every(level => interviews.some(i => i.level === level))) && (
|
||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="w-full"
|
className="w-full"
|
||||||
@ -2717,9 +2790,26 @@ export function ApplicationDetails() {
|
|||||||
<SelectValue placeholder="Select interview type" />
|
<SelectValue placeholder="Select interview type" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="level1">Level 1</SelectItem>
|
<SelectItem value="level1" disabled={isInterviewCompleted(1)}>
|
||||||
<SelectItem value="level2">Level 2</SelectItem>
|
<div className="flex items-center justify-between w-full">
|
||||||
<SelectItem value="level3">Level 3</SelectItem>
|
<span>Level 1</span>
|
||||||
|
{isInterviewCompleted(1) && <CheckCircle className="w-4 h-4 text-green-500 ml-2 inline" />}
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="level2" disabled={!isInterviewCompleted(1) || isInterviewCompleted(2)}>
|
||||||
|
<div className="flex items-center justify-between w-full">
|
||||||
|
<span>Level 2</span>
|
||||||
|
{!isInterviewCompleted(1) && <span className="text-[10px] text-slate-400 ml-2">(Prerequisite: L1)</span>}
|
||||||
|
{isInterviewCompleted(2) && <CheckCircle className="w-4 h-4 text-green-500 ml-2 inline" />}
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
|
<SelectItem value="level3" disabled={!isInterviewCompleted(2) || isInterviewCompleted(3)}>
|
||||||
|
<div className="flex items-center justify-between w-full">
|
||||||
|
<span>Level 3</span>
|
||||||
|
{!isInterviewCompleted(2) && <span className="text-[10px] text-slate-400 ml-2">(Prerequisite: L2)</span>}
|
||||||
|
{isInterviewCompleted(3) && <CheckCircle className="w-4 h-4 text-green-500 ml-2 inline" />}
|
||||||
|
</div>
|
||||||
|
</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
</Select>
|
</Select>
|
||||||
</div>
|
</div>
|
||||||
@ -2778,7 +2868,7 @@ export function ApplicationDetails() {
|
|||||||
<SelectContent>
|
<SelectContent>
|
||||||
{users.map((user) => (
|
{users.map((user) => (
|
||||||
<SelectItem key={user.id} value={user.id}>
|
<SelectItem key={user.id} value={user.id}>
|
||||||
{user.fullName} ({user.role?.roleName || user.roleCode})
|
{user.fullName || user.name} ({user.role?.roleName || user.roleCode})
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
))}
|
))}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@ -2795,7 +2885,7 @@ export function ApplicationDetails() {
|
|||||||
<div className="flex flex-wrap gap-2">
|
<div className="flex flex-wrap gap-2">
|
||||||
{scheduledInterviewParticipants.map((p) => (
|
{scheduledInterviewParticipants.map((p) => (
|
||||||
<div key={p.id} className="flex items-center gap-1 bg-secondary px-2 py-1 rounded text-sm">
|
<div key={p.id} className="flex items-center gap-1 bg-secondary px-2 py-1 rounded text-sm">
|
||||||
<span>{p.fullName}</span>
|
<span>{p.fullName || p.name || 'Unknown'}</span>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleRemoveInterviewer(p.id)}
|
onClick={() => handleRemoveInterviewer(p.id)}
|
||||||
className="text-muted-foreground hover:text-destructive"
|
className="text-muted-foreground hover:text-destructive"
|
||||||
@ -2819,7 +2909,10 @@ export function ApplicationDetails() {
|
|||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1 bg-amber-600 hover:bg-amber-700"
|
className="flex-1 bg-amber-600 hover:bg-amber-700"
|
||||||
onClick={handleScheduleInterview}
|
onClick={() => {
|
||||||
|
// Ensure we pass participants to the schedule handler
|
||||||
|
handleScheduleInterview();
|
||||||
|
}}
|
||||||
disabled={isScheduling}
|
disabled={isScheduling}
|
||||||
>
|
>
|
||||||
{isScheduling ? 'Scheduling...' : 'Schedule'}
|
{isScheduling ? 'Scheduling...' : 'Schedule'}
|
||||||
@ -3008,7 +3101,7 @@ export function ApplicationDetails() {
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1 bg-amber-600 hover:bg-amber-700"
|
className="flex-1 bg-black hover:bg-zinc-800 text-white"
|
||||||
onClick={handleSubmitKTMatrix}
|
onClick={handleSubmitKTMatrix}
|
||||||
disabled={isSubmittingKT}
|
disabled={isSubmittingKT}
|
||||||
>
|
>
|
||||||
@ -3031,12 +3124,22 @@ export function ApplicationDetails() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<Label>Interview Date</Label>
|
<Label>Interview Date</Label>
|
||||||
<Input type="date" className="mt-2" />
|
<Input
|
||||||
|
type="date"
|
||||||
|
className="mt-2"
|
||||||
|
value={level2Feedback.interviewDate}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>Interviewer Name</Label>
|
<Label>Interviewer Name</Label>
|
||||||
<Input placeholder="Enter your name" className="mt-2" />
|
<Input
|
||||||
|
placeholder="Enter your name"
|
||||||
|
className="mt-2"
|
||||||
|
value={level2Feedback.interviewerName}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -3137,7 +3240,7 @@ export function ApplicationDetails() {
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1 bg-blue-600 hover:bg-blue-700"
|
className="flex-1 bg-black hover:bg-zinc-800 text-white"
|
||||||
onClick={handleSubmitLevel2Feedback}
|
onClick={handleSubmitLevel2Feedback}
|
||||||
disabled={isSubmittingLevel2}
|
disabled={isSubmittingLevel2}
|
||||||
>
|
>
|
||||||
@ -3220,12 +3323,22 @@ export function ApplicationDetails() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<Label>Interview Date</Label>
|
<Label>Interview Date</Label>
|
||||||
<Input type="date" className="mt-2" />
|
<Input
|
||||||
|
type="date"
|
||||||
|
className="mt-2"
|
||||||
|
value={level3Feedback.interviewDate}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>Interviewer Name</Label>
|
<Label>Interviewer Name</Label>
|
||||||
<Input placeholder="Enter your name" className="mt-2" />
|
<Input
|
||||||
|
placeholder="Enter your name"
|
||||||
|
className="mt-2"
|
||||||
|
value={level3Feedback.interviewerName}
|
||||||
|
disabled
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -3315,23 +3428,6 @@ export function ApplicationDetails() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
|
||||||
<Label>Final Recommendation</Label>
|
|
||||||
<Select
|
|
||||||
value={level3Feedback.recommendation}
|
|
||||||
onValueChange={(value) => handleLevel3Change('recommendation', value)}
|
|
||||||
>
|
|
||||||
<SelectTrigger className="mt-2">
|
|
||||||
<SelectValue placeholder="Select recommendation" />
|
|
||||||
</SelectTrigger>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItem value="Approve">Approve for Onboarding</SelectItem>
|
|
||||||
<SelectItem value="Hold">Hold Decision</SelectItem>
|
|
||||||
<SelectItem value="Reject">Reject</SelectItem>
|
|
||||||
</SelectContent>
|
|
||||||
</Select>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<Label>Executive Summary</Label>
|
<Label>Executive Summary</Label>
|
||||||
<Textarea
|
<Textarea
|
||||||
@ -3352,7 +3448,7 @@ export function ApplicationDetails() {
|
|||||||
Cancel
|
Cancel
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
className="flex-1 bg-green-700 hover:bg-green-800"
|
className="flex-1 bg-black hover:bg-zinc-800 text-white"
|
||||||
onClick={handleSubmitLevel3Feedback}
|
onClick={handleSubmitLevel3Feedback}
|
||||||
disabled={isSubmittingLevel3}
|
disabled={isSubmittingLevel3}
|
||||||
>
|
>
|
||||||
@ -3566,7 +3662,9 @@ export function ApplicationDetails() {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</DialogContent>
|
</DialogContent>
|
||||||
</Dialog >
|
</Dialog>
|
||||||
</div >
|
</div>
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
|
|
||||||
|
export default ApplicationDetails;
|
||||||
|
|||||||
@ -98,7 +98,7 @@ export const MasterPage: React.FC = () => {
|
|||||||
const [zoneName, setZoneName] = useState('');
|
const [zoneName, setZoneName] = useState('');
|
||||||
const [zoneCode, setZoneCode] = useState('');
|
const [zoneCode, setZoneCode] = useState('');
|
||||||
const [zoneDescription, setZoneDescription] = useState('');
|
const [zoneDescription, setZoneDescription] = useState('');
|
||||||
const [zonalBusinessHeadId, setZonalBusinessHeadId] = useState('');
|
const [zonalBusinessHeadId, setZonalBusinessHeadId] = useState('none');
|
||||||
|
|
||||||
// Form State (Region)
|
// Form State (Region)
|
||||||
const [editingRegionId, setEditingRegionId] = useState<string | null>(null);
|
const [editingRegionId, setEditingRegionId] = useState<string | null>(null);
|
||||||
@ -288,7 +288,13 @@ export const MasterPage: React.FC = () => {
|
|||||||
|
|
||||||
const handleSaveZone = async () => {
|
const handleSaveZone = async () => {
|
||||||
try {
|
try {
|
||||||
const payload = { id: editingZoneId, name: zoneName, code: zoneCode, description: zoneDescription, managerId: zonalBusinessHeadId };
|
const payload = {
|
||||||
|
id: editingZoneId,
|
||||||
|
name: zoneName,
|
||||||
|
code: zoneCode,
|
||||||
|
description: zoneDescription,
|
||||||
|
managerId: zonalBusinessHeadId === 'none' ? null : zonalBusinessHeadId
|
||||||
|
};
|
||||||
const res = await masterService.saveZone(payload) as any;
|
const res = await masterService.saveZone(payload) as any;
|
||||||
if (res.success) {
|
if (res.success) {
|
||||||
toast.success('Zone saved successfully');
|
toast.success('Zone saved successfully');
|
||||||
@ -456,8 +462,8 @@ export const MasterPage: React.FC = () => {
|
|||||||
|
|
||||||
<TabsContent value="hierarchy" className="space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-300">
|
<TabsContent value="hierarchy" className="space-y-8 animate-in fade-in slide-in-from-bottom-2 duration-300">
|
||||||
<ZonesOverview selectedZone={selectedZone} onZoneClick={(id) => setSelectedZone(selectedZone === id ? 'all' : id)} />
|
<ZonesOverview selectedZone={selectedZone} onZoneClick={(id) => setSelectedZone(selectedZone === id ? 'all' : id)} />
|
||||||
<ZoneDetails selectedZone={selectedZone} onAddZone={() => { setEditingZoneId(null); setZoneName(''); setZoneCode(''); setZoneDescription(''); setZonalBusinessHeadId(''); setShowZoneDialog(true); }}
|
<ZoneDetails selectedZone={selectedZone} onAddZone={() => { setEditingZoneId(null); setZoneName(''); setZoneCode(''); setZoneDescription(''); setZonalBusinessHeadId('none'); setShowZoneDialog(true); }}
|
||||||
onEditZone={(z) => { setEditingZoneId(z.id); setZoneName(z.name); setZoneCode(z.code); setZoneDescription(z.description); setZonalBusinessHeadId(z.zonalBusinessHead?.id || ''); setShowZoneDialog(true); }} />
|
onEditZone={(z) => { setEditingZoneId(z.id); setZoneName(z.name); setZoneCode(z.code); setZoneDescription(z.description); setZonalBusinessHeadId(z.zonalBusinessHead?.id || 'none'); setShowZoneDialog(true); }} />
|
||||||
|
|
||||||
<RegionalManagement selectedZone={selectedZone} onAddRegion={() => { setEditingRegionId(null); setRegionName(''); setRegionCode(''); setSelectedRegionZone(selectedZone === 'all' ? '' : selectedZone); setRegionalManagerId(''); setSelectedRegionDistricts([]); setShowRegionDialog(true); }}
|
<RegionalManagement selectedZone={selectedZone} onAddRegion={() => { setEditingRegionId(null); setRegionName(''); setRegionCode(''); setSelectedRegionZone(selectedZone === 'all' ? '' : selectedZone); setRegionalManagerId(''); setSelectedRegionDistricts([]); setShowRegionDialog(true); }}
|
||||||
onEditRegion={(r) => {
|
onEditRegion={(r) => {
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import React from 'react';
|
|||||||
import { Card, CardContent, CardHeader, CardTitle } from '../../ui/card';
|
import { Card, CardContent, CardHeader, CardTitle } from '../../ui/card';
|
||||||
import { Badge } from '../../ui/badge';
|
import { Badge } from '../../ui/badge';
|
||||||
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../ui/table';
|
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '../../ui/table';
|
||||||
import { Shield, User, Mail, MapPin } from 'lucide-react';
|
import { Shield, User, Mail } from 'lucide-react';
|
||||||
|
|
||||||
interface UserManagementTableProps {
|
interface UserManagementTableProps {
|
||||||
userAssignedData: any[];
|
userAssignedData: any[];
|
||||||
@ -22,7 +22,6 @@ export const UserManagementTable: React.FC<UserManagementTableProps> = ({ userAs
|
|||||||
<TableHead>Role</TableHead>
|
<TableHead>Role</TableHead>
|
||||||
<TableHead>Assigned Zone</TableHead>
|
<TableHead>Assigned Zone</TableHead>
|
||||||
<TableHead>Assigned Region</TableHead>
|
<TableHead>Assigned Region</TableHead>
|
||||||
<TableHead>Location Type</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
<TableHead>Status</TableHead>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
@ -57,12 +56,6 @@ export const UserManagementTable: React.FC<UserManagementTableProps> = ({ userAs
|
|||||||
{user.region}
|
{user.region}
|
||||||
</Badge>
|
</Badge>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
|
||||||
<div className="flex items-center gap-1.5 text-slate-600">
|
|
||||||
<MapPin className="w-3.5 h-3.5" />
|
|
||||||
<span className="text-sm capitalize">{user.locationType || 'N/A'}</span>
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<Badge variant={user.status === 'Active' ? 'default' : 'secondary'} className={user.status === 'Active' ? 'bg-emerald-100 text-emerald-700' : ''}>
|
<Badge variant={user.status === 'Active' ? 'default' : 'secondary'} className={user.status === 'Active' ? 'bg-emerald-100 text-emerald-700' : ''}>
|
||||||
{user.status}
|
{user.status}
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export const ZMManagement: React.FC<ZMManagementProps> = ({
|
|||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<CardTitle>Zonal Managers (ZM)</CardTitle>
|
<CardTitle>Zonal Managers (ZM)</CardTitle>
|
||||||
<CardDescription>Manage Zonal Managers and their district assignments</CardDescription>
|
<CardDescription>Manage Zonal Managers and their region assignments</CardDescription>
|
||||||
</div>
|
</div>
|
||||||
<Button onClick={onAddZM} className="bg-amber-600 hover:bg-amber-700">
|
<Button onClick={onAddZM} className="bg-amber-600 hover:bg-amber-700">
|
||||||
<Plus className="w-4 h-4 mr-2" />
|
<Plus className="w-4 h-4 mr-2" />
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import { Button } from '../../ui/button';
|
|||||||
import { Badge } from '../../ui/badge';
|
import { Badge } from '../../ui/badge';
|
||||||
import { ScrollArea } from '../../ui/scroll-area';
|
import { ScrollArea } from '../../ui/scroll-area';
|
||||||
import { Label } from '../../ui/label';
|
import { Label } from '../../ui/label';
|
||||||
import { Globe, Plus, Edit2, Mail, Users, MapPin } from 'lucide-react';
|
import { Globe, Plus, Edit2, Mail, Users, Shield } from 'lucide-react';
|
||||||
import { RootState } from '../../../store';
|
import { RootState } from '../../../store';
|
||||||
|
|
||||||
interface ZoneDetailsProps {
|
interface ZoneDetailsProps {
|
||||||
@ -81,6 +81,26 @@ export const ZoneDetails: React.FC<ZoneDetailsProps> = ({ selectedZone, onAddZon
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{zone.zonalBusinessHead && (
|
||||||
|
<div className="border-t pt-3">
|
||||||
|
<Label className="text-xs text-slate-600 mb-2 block">
|
||||||
|
Zonal Business Head (ZBH)
|
||||||
|
</Label>
|
||||||
|
<div className="bg-amber-50 border border-amber-100 rounded-lg p-3 space-y-2">
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Shield className="w-4 h-4 text-amber-600" />
|
||||||
|
<span className="text-sm font-semibold text-slate-900">{zone.zonalBusinessHead.name}</span>
|
||||||
|
<Badge className="bg-amber-600 text-white text-[10px] ml-auto">ZBH</Badge>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2 ml-6 text-slate-600">
|
||||||
|
<Mail className="w-3 h-3" />
|
||||||
|
<span className="text-xs">{zone.zonalBusinessHead.email}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{zone.zonalManagers && zone.zonalManagers.length > 0 && (
|
{zone.zonalManagers && zone.zonalManagers.length > 0 && (
|
||||||
<div className="border-t pt-3">
|
<div className="border-t pt-3">
|
||||||
<Label className="text-xs text-slate-600 mb-2 block">
|
<Label className="text-xs text-slate-600 mb-2 block">
|
||||||
@ -108,20 +128,20 @@ export const ZoneDetails: React.FC<ZoneDetailsProps> = ({ selectedZone, onAddZon
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{zm.districts && zm.districts.length > 0 && (
|
{zm.regions && zm.regions.length > 0 && (
|
||||||
<div className="ml-6 mt-2">
|
<div className="ml-6 mt-2">
|
||||||
<Label className="text-xs text-slate-500 mb-1 block">
|
<Label className="text-xs text-slate-500 mb-1 block">
|
||||||
Assigned Districts ({zm.districts.length})
|
Managed Regions ({zm.regions.length})
|
||||||
</Label>
|
</Label>
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{zm.districts.map((district: string, dIdx: number) => (
|
{zm.regions.map((region: string, rIdx: number) => (
|
||||||
<Badge
|
<Badge
|
||||||
key={dIdx}
|
key={rIdx}
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="text-xs bg-white text-foreground"
|
className="text-xs bg-white text-foreground"
|
||||||
>
|
>
|
||||||
<MapPin className="w-2.5 h-2.5 mr-1" />
|
<Globe className="w-2.5 h-2.5 mr-1" />
|
||||||
{district}
|
{region}
|
||||||
</Badge>
|
</Badge>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -28,11 +28,15 @@ export const ZoneDialog: React.FC<ZoneDialogProps> = ({
|
|||||||
zonalBusinessHeadId, setZonalBusinessHeadId, userAssignedData, onSave
|
zonalBusinessHeadId, setZonalBusinessHeadId, userAssignedData, onSave
|
||||||
}) => {
|
}) => {
|
||||||
const filteredZBHUsers = (userAssignedData || []).filter((u: any) => {
|
const filteredZBHUsers = (userAssignedData || []).filter((u: any) => {
|
||||||
|
// Always include the currently assigned head to ensure pre-filling works
|
||||||
|
if (zonalBusinessHeadId !== 'none' && u.id === zonalBusinessHeadId) return true;
|
||||||
|
|
||||||
const roles = u.allRoles || [];
|
const roles = u.allRoles || [];
|
||||||
return roles.some((r: string) => {
|
const topLevelRole = (u.roleCode || '').toUpperCase();
|
||||||
|
|
||||||
|
return topLevelRole === 'ZBH' || roles.some((r: string) => {
|
||||||
const roleStr = (r || '').toUpperCase();
|
const roleStr = (r || '').toUpperCase();
|
||||||
return ['ZBH', 'ZONE BUSINESS HEAD', 'ZONAL BUSINESS HEAD', 'RM', 'RBM', 'REGIONAL MANAGER', 'ASM', 'AREA SALES MANAGER'].includes(roleStr) ||
|
return roleStr === 'ZBH' || roleStr === 'ZONE BUSINESS HEAD' || roleStr === 'ZONAL BUSINESS HEAD';
|
||||||
roleStr.includes('ZONAL') || roleStr.includes('REGIONAL') || roleStr.includes('AREA SALES');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -65,7 +65,7 @@ export const useMasterData = () => {
|
|||||||
|
|
||||||
const zones = (bodyZones?.zones || bodyZones?.data || []).map((z: any) => {
|
const zones = (bodyZones?.zones || bodyZones?.data || []).map((z: any) => {
|
||||||
const zoneName = (z.name || z.zoneName || '').toUpperCase();
|
const zoneName = (z.name || z.zoneName || '').toUpperCase();
|
||||||
const zoneUsers = users.filter((u: any) => u.allZones?.includes(zoneName));
|
// const zoneUsers = users.filter((u: any) => u.allZones?.includes(zoneName));
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: z.id, name: zoneName, description: z.description || '',
|
id: z.id, name: zoneName, description: z.description || '',
|
||||||
@ -75,17 +75,17 @@ export const useMasterData = () => {
|
|||||||
regionalOfficerCount: z.regionalOfficerCount || 0,
|
regionalOfficerCount: z.regionalOfficerCount || 0,
|
||||||
zmCount: z.zmCount || 0,
|
zmCount: z.zmCount || 0,
|
||||||
states: z.states || [],
|
states: z.states || [],
|
||||||
zbh: {
|
zonalBusinessHead: {
|
||||||
name: z.zonalBusinessHead?.fullName || 'Not Assigned',
|
name: z.zonalBusinessHead?.name || z.zonalBusinessHead?.fullName || 'Not Assigned',
|
||||||
email: z.zonalBusinessHead?.email || '',
|
email: z.zonalBusinessHead?.email || '',
|
||||||
phone: z.zonalBusinessHead?.mobileNumber || ''
|
phone: z.zonalBusinessHead?.mobileNumber || z.zonalBusinessHead?.phone || ''
|
||||||
},
|
},
|
||||||
zonalManagers: (z.zonalManagers || []).map((m: any) => ({
|
zonalManagers: (z.zonalManagers || []).map((m: any) => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
name: m.name || m.fullName || 'Unknown',
|
name: m.name || m.fullName || 'Unknown',
|
||||||
email: m.email || '',
|
email: m.email || '',
|
||||||
phone: m.phone || m.mobileNumber || '',
|
phone: m.phone || m.mobileNumber || '',
|
||||||
districts: m.districts || []
|
regions: m.regions || []
|
||||||
}))
|
}))
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
@ -116,6 +116,7 @@ export interface Application {
|
|||||||
zoneId?: string;
|
zoneId?: string;
|
||||||
regionId?: string;
|
regionId?: string;
|
||||||
areaId?: string;
|
areaId?: string;
|
||||||
|
districtId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Participant {
|
export interface Participant {
|
||||||
|
|||||||
@ -5,9 +5,9 @@ import { toast } from 'sonner';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { RootState } from '../../store';
|
import { RootState } from '../../store';
|
||||||
import {
|
import {
|
||||||
User, RefreshCw, HelpCircle, Bell, ArrowLeft, Bike,
|
User, RefreshCw, HelpCircle, ArrowLeft, Bike,
|
||||||
Users, Target, FileText, Award, ChevronLeft, ChevronRight,
|
Users, FileText, ChevronRight,
|
||||||
CheckCircle, AlertCircle
|
CheckCircle
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
const PublicQuestionnairePage: React.FC = () => {
|
const PublicQuestionnairePage: React.FC = () => {
|
||||||
@ -231,7 +231,7 @@ const PublicQuestionnairePage: React.FC = () => {
|
|||||||
{/* Section Tabs */}
|
{/* Section Tabs */}
|
||||||
<div className="bg-slate-800/50 backdrop-blur-sm border-t border-slate-700">
|
<div className="bg-slate-800/50 backdrop-blur-sm border-t border-slate-700">
|
||||||
<div className="flex items-center gap-2 overflow-x-auto scrollbar-hide px-8 py-4 no-scrollbar">
|
<div className="flex items-center gap-2 overflow-x-auto scrollbar-hide px-8 py-4 no-scrollbar">
|
||||||
{sections.map((section, idx) => (
|
{sections.map((section) => (
|
||||||
<button
|
<button
|
||||||
key={section}
|
key={section}
|
||||||
onClick={() => setActiveSection(section)}
|
onClick={() => setActiveSection(section)}
|
||||||
@ -300,19 +300,33 @@ const PublicQuestionnairePage: React.FC = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(q.inputType === 'select' || q.inputType === 'yesno') && (
|
{q.inputType === 'textarea' && (
|
||||||
<select
|
<textarea
|
||||||
className="w-full h-10 px-3 rounded-lg border border-slate-300 focus:border-amber-500 focus:ring-2 focus:ring-amber-200 outline-none transition-all bg-white"
|
className="w-full h-32 p-3 rounded-lg border border-slate-300 focus:border-amber-500 focus:ring-2 focus:ring-amber-200 outline-none transition-all placeholder:text-slate-400"
|
||||||
|
placeholder="Type your answer here..."
|
||||||
value={responses[q.id] || ''}
|
value={responses[q.id] || ''}
|
||||||
onChange={(e) => handleInputChange(q.id, e.target.value)}
|
onChange={(e) => handleInputChange(q.id, e.target.value)}
|
||||||
>
|
/>
|
||||||
<option value="">Select an option...</option>
|
)}
|
||||||
{(q.questionOptions || (q.inputType === 'yesno' ? [{ optionText: 'Yes' }, { optionText: 'No' }] : [])).map((opt: any, i: number) => (
|
|
||||||
<option key={i} value={opt.optionText || opt.text}>
|
{(q.inputType === 'select' || q.inputType === 'yesno' || q.inputType === 'radio' || q.inputType === 'mcq') && (
|
||||||
{opt.optionText || opt.text}
|
<div className="space-y-2">
|
||||||
</option>
|
{(q.questionOptions || (q.inputType === 'yesno' ? [{ optionText: 'Yes' }, { optionText: 'No' }] : [])).map((opt: any, i: number) => {
|
||||||
))}
|
const val = opt.optionText || opt.text;
|
||||||
</select>
|
return (
|
||||||
|
<label key={i} className="flex items-center gap-3 cursor-pointer group/opt">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name={`q-${q.id}`}
|
||||||
|
className="w-4 h-4 text-amber-600 focus:ring-amber-500 border-slate-300"
|
||||||
|
checked={responses[q.id] === val}
|
||||||
|
onChange={() => handleInputChange(q.id, val)}
|
||||||
|
/>
|
||||||
|
<span className="text-slate-700 group-hover/opt:text-slate-900 transition-colors">{val}</span>
|
||||||
|
</label>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -188,5 +188,14 @@ export const onboardingService = {
|
|||||||
console.error('Create dealer error:', error);
|
console.error('Create dealer error:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
retriggerEvaluators: async (id: string) => {
|
||||||
|
try {
|
||||||
|
const response: any = await API.retriggerEvaluators(id);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Retrigger evaluators error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,11 +6,10 @@ export interface Zone {
|
|||||||
description?: string;
|
description?: string;
|
||||||
code: string;
|
code: string;
|
||||||
regionCount: number;
|
regionCount: number;
|
||||||
districtCount: number;
|
|
||||||
states: string[];
|
|
||||||
zmCount: number;
|
zmCount: number;
|
||||||
zonalBusinessHead: { id: string; name: string; email: string; phone: string } | null;
|
states: string[];
|
||||||
zonalManagers: { id: string; name: string; email: string; phone: string; districts: string[] }[];
|
zonalBusinessHead?: { id: string; name: string; email: string; phone?: string };
|
||||||
|
zonalManagers: { id: string; name: string; email: string; phone: string; regions: string[] }[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Region {
|
export interface Region {
|
||||||
@ -21,6 +20,7 @@ export interface Region {
|
|||||||
zoneName: string;
|
zoneName: string;
|
||||||
districts: { id: string; name: string, stateId?: string }[];
|
districts: { id: string; name: string, stateId?: string }[];
|
||||||
states: string[];
|
states: string[];
|
||||||
|
cities: string[];
|
||||||
status: string;
|
status: string;
|
||||||
regionalOfficerCount: number;
|
regionalOfficerCount: number;
|
||||||
asmCount: number;
|
asmCount: number;
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user