multi iteration done in approval flow for progress stsus

This commit is contained in:
laxman h 2026-04-01 18:03:19 +05:30
parent e68f96a929
commit 7fa34dd3d6
10 changed files with 730 additions and 495 deletions

View File

@ -69,6 +69,7 @@ export const API = {
getInterviews: (applicationId: string) => client.get(`/assessment/interviews/${applicationId}`),
updateRecommendation: (data: any) => client.post('/assessment/recommendation', data),
updateInterviewDecision: (data: any) => client.post('/assessment/decision', data),
submitStageDecision: (data: any) => client.post('/assessment/stage-decision', data),
getInterviewApprovalStatus: (interviewId: string) => client.get(`/assessment/interviews/${interviewId}/approval-status`),
getApprovalPolicies: () => client.get('/assessment/approval-policies'),
upsertApprovalPolicy: (stageCode: string, data: any) => client.put(`/assessment/approval-policies/${stageCode}`, data),

View File

@ -8,7 +8,7 @@ const client = create({
'Content-Type': 'application/json',
'Accept': 'application/json',
},
timeout: 10000,
timeout: 30000,
});
// Interceptor for Auth Token
@ -23,7 +23,13 @@ client.addRequestTransform((request) => {
client.addResponseTransform((response) => {
if (!response.ok) {
if (response.status === 401) {
console.error('Unauthorized access - potential token expiration');
const token = localStorage.getItem('token');
console.error('Unauthorized access - potential token expiration. Token exists in localStorage:', !!token);
console.error('Full 401 Response Details:', {
url: response.config?.url,
method: response.config?.method,
data: response.data
});
// Dispatch global event for App to handle logout
window.dispatchEvent(new Event('auth:logout'));
}

View File

@ -46,7 +46,6 @@ export function UserManagementPage() {
const [regions, setRegions] = useState<any[]>([]);
const [states, setStates] = useState<any[]>([]);
const [districts, setDistricts] = useState<any[]>([]);
const [areas, setAreas] = useState<any[]>([]);
const [loading, setLoading] = useState(true);
const [searchQuery, setSearchQuery] = useState('');
@ -69,8 +68,7 @@ export function UserManagementPage() {
zoneId: '',
regionId: '',
stateId: '',
districtId: '',
areaId: ''
districtId: ''
});
useEffect(() => {
@ -120,15 +118,9 @@ export function UserManagementPage() {
}
}, [formData.stateId]);
// Load areas when district changes
// Load areas when district changes (Disabled)
useEffect(() => {
if (formData.districtId) {
masterService.getAreas(formData.districtId).then((res: any) => {
if (res && res.success) setAreas(normalizeList(res, 'areas'));
});
} else {
setAreas([]);
}
// Area selection removed as per user request
}, [formData.districtId]);
const handleEditUser = (user: any) => {
@ -139,8 +131,7 @@ export function UserManagementPage() {
const regionId = user.regionId || (userLocationType === 'region' ? userLocation?.id : getParentIdByType(userLocation, 'region'));
const stateId = user.stateId || (userLocationType === 'state' ? userLocation?.id : getParentIdByType(userLocation, 'state'));
const districtId = user.districtId || (userLocationType === 'district' ? userLocation?.id : getParentIdByType(userLocation, 'district'));
const areaId = user.areaId || (userLocationType === 'area' ? userLocation?.id : '');
setEditingUser(user);
setFormData({
fullName: user.fullName || '',
@ -155,8 +146,7 @@ export function UserManagementPage() {
zoneId: zoneId || '',
regionId: regionId || '',
stateId: stateId || '',
districtId: districtId || '',
areaId: areaId || ''
districtId: districtId || ''
});
setShowUserModal(true);
};
@ -168,7 +158,7 @@ export function UserManagementPage() {
}
try {
const userLocationId = formData.areaId || formData.districtId || formData.stateId || formData.regionId || formData.zoneId || null;
const userLocationId = formData.districtId || formData.stateId || formData.regionId || formData.zoneId || null;
const submitData = {
...formData,
locationId: userLocationId
@ -187,7 +177,7 @@ export function UserManagementPage() {
setFormData({
fullName: '', email: '', roleCode: '', status: 'active', isActive: true,
mobileNumber: '', department: '', designation: '', employeeId: '',
zoneId: '', regionId: '', stateId: '', districtId: '', areaId: ''
zoneId: '', regionId: '', stateId: '', districtId: ''
});
setShowUserModal(false);
fetchData();
@ -236,7 +226,7 @@ export function UserManagementPage() {
setEditingUser(null); setFormData({
fullName: '', email: '', roleCode: '', status: 'active', isActive: true,
mobileNumber: '', department: '', designation: '', employeeId: '',
zoneId: '', regionId: '', stateId: '', districtId: '', areaId: ''
zoneId: '', regionId: '', stateId: '', districtId: ''
}); setShowUserModal(true);
}}
className="bg-amber-600 hover:bg-amber-700 text-white shrink-0"
@ -506,7 +496,7 @@ export function UserManagementPage() {
<Label htmlFor="zoneId">Zone (Top Level)</Label>
<Select
value={formData.zoneId}
onValueChange={(val) => setFormData({ ...formData, zoneId: val, regionId: '', stateId: '', districtId: '', areaId: '' })}
onValueChange={(val) => setFormData({ ...formData, zoneId: val, regionId: '', stateId: '', districtId: '' })}
>
<SelectTrigger id="zoneId">
<SelectValue placeholder="Select Zone" />
@ -539,7 +529,7 @@ export function UserManagementPage() {
<Label htmlFor="stateId">State</Label>
<Select
value={formData.stateId}
onValueChange={(val) => setFormData({ ...formData, stateId: val, districtId: '', areaId: '' })}
onValueChange={(val) => setFormData({ ...formData, stateId: val, districtId: '' })}
disabled={!formData.zoneId}
>
<SelectTrigger id="stateId">
@ -556,7 +546,7 @@ export function UserManagementPage() {
<Label htmlFor="districtId">District</Label>
<Select
value={formData.districtId}
onValueChange={(val) => setFormData({ ...formData, districtId: val, areaId: '' })}
onValueChange={(val) => setFormData({ ...formData, districtId: val })}
disabled={!formData.stateId}
>
<SelectTrigger id="districtId">
@ -569,23 +559,6 @@ export function UserManagementPage() {
</SelectContent>
</Select>
</div>
<div className="grid gap-2">
<Label htmlFor="areaId">Area</Label>
<Select
value={formData.areaId}
onValueChange={(val) => setFormData({ ...formData, areaId: val })}
disabled={!formData.districtId}
>
<SelectTrigger id="areaId">
<SelectValue placeholder="Select Area" />
</SelectTrigger>
<SelectContent>
{areas.filter(a => (a.parents && a.parents.some((p:any) => p.id === formData.districtId)) || a.districtId === formData.districtId).map(area => (
<SelectItem key={area.id} value={area.id}>{area.name || area.areaName}</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
</div>
</div>

File diff suppressed because it is too large Load Diff

View File

@ -152,17 +152,24 @@ export function WorkNotesPage(props: Partial<WorkNotesPageProps>) {
// Map backend participants to the UI sub-format
// Handles both nested structure { user: { id, fullName } } and flat { id, userId, name/fullName }
// NOTE: p.id is the RequestParticipant record ID. p.userId or p.user?.id is the actual User ID.
const participantsList: ParticipantUI[] = externalParticipants.map((p: any) => {
// Map backend participants to the UI sub-format and ensure uniqueness by User ID to avoid duplicates in mentions
const participantsList: ParticipantUI[] = [];
const seenIds = new Set<string>();
externalParticipants.forEach((p: any) => {
const id = p.user?.id || p.userId || p.id || '';
const name = p.user?.fullName || p.user?.name || p.fullName || p.name || 'Unknown User';
const email = p.user?.email || p.email || '';
return {
id,
name,
email,
initials: getInitials(name),
color: getAvatarColor(name)
};
if (id && !seenIds.has(id)) {
seenIds.add(id);
const name = p.user?.fullName || p.user?.name || p.fullName || p.name || 'Unknown User';
const email = p.user?.email || p.email || '';
participantsList.push({
id,
name,
email,
initials: getInitials(name),
color: getAvatarColor(name)
});
}
});
console.log('Participants list for mentions:', participantsList.map(p => ({ id: p.id, name: p.name })));

View File

@ -3,7 +3,7 @@ import { Button } from '../ui/button';
import { Input } from '../ui/input';
import { Label } from '../ui/label';
import { Checkbox } from '../ui/checkbox';
import { AlertCircle, Copy, Check } from 'lucide-react';
import { AlertCircle, Copy, Check, Eye, EyeOff } from 'lucide-react';
import { mockUsers } from '../../lib/mock-data';
import { toast } from 'sonner';
@ -18,6 +18,7 @@ export function LoginPage({ onLogin }: LoginPageProps) {
const [error, setError] = useState('');
const [showForgotPassword, setShowForgotPassword] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
const [copiedIndex, setCopiedIndex] = useState<number | null>(null);
const copyToClipboard = async (text: string, index: number) => {
@ -144,15 +145,29 @@ export function LoginPage({ onLogin }: LoginPageProps) {
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full"
disabled={isLoading}
/>
<div className="relative">
<Input
id="password"
type={showPassword ? "text" : "password"}
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="w-full pr-10"
disabled={isLoading}
/>
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="absolute right-3 top-1/2 -translate-y-1/2 text-slate-400 hover:text-slate-600 focus:outline-none"
disabled={isLoading}
>
{showPassword ? (
<EyeOff className="w-5 h-5" />
) : (
<Eye className="w-5 h-5" />
)}
</button>
</div>
</div>
<div className="flex items-center justify-between">

View File

@ -6,7 +6,7 @@ import { Textarea } from '../ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
import { RadioGroup, RadioGroupItem } from '../ui/radio-group';
import { Checkbox } from '../ui/checkbox';
import { CheckCircle, Users, Star, Shield, LogIn, Award, TrendingUp, Handshake } from 'lucide-react';
import { CheckCircle, Users, Star, LogIn, Award, TrendingUp, Handshake } from 'lucide-react';
import { toast } from 'sonner';
// import backgroundImage from 'figma:asset/ee01d864b6e23a8197b42f3168c98eedec9d2440.png';
import { onboardingService } from '../../services/onboarding.service';
@ -52,7 +52,9 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
try {
// ZoneID is optional, public API should return all states if no zone override
const response: any = await masterService.getStates();
if (response && response.states) {
if (response && response.data) {
setStates(response.data);
} else if (response && response.states) {
setStates(response.states);
}
} catch (error) {
@ -72,7 +74,9 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
try {
const response: any = await masterService.getDistricts(selectedState.id);
if (response && response.districts) {
if (response && response.data) {
setDistricts(response.data);
} else if (response && response.districts) {
setDistricts(response.districts);
}
} catch (error) {
@ -112,8 +116,8 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
try {
const selectedState = states.find((s: any) => s.id === formData.stateId);
const selectedDistrict = districts.find((d: any) => d.id === formData.districtId);
const stateName = selectedState?.stateName || '';
const districtName = selectedDistrict?.districtName || '';
const stateName = selectedState?.name || selectedState?.stateName || '';
const districtName = selectedDistrict?.name || selectedDistrict?.districtName || '';
// Map form data to backend expected format
const payload = {
@ -409,7 +413,7 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
<SelectContent className="bg-slate-800 border-slate-700 text-white h-64">
{states.map((state: any) => (
<SelectItem key={state.id} value={state.id}>
{state.stateName}
{state.name || state.stateName}
</SelectItem>
))}
</SelectContent>
@ -431,7 +435,7 @@ export function ApplicationFormPage({ onAdminLogin }: ApplicationFormPageProps)
<SelectContent className="bg-slate-800 border-slate-700 text-white h-64">
{districts.map((district: any) => (
<SelectItem key={district.id} value={district.id}>
{district.districtName}
{district.name || district.districtName}
</SelectItem>
))}
</SelectContent>

View File

@ -57,7 +57,8 @@ export type ApplicationStatus =
| 'Inauguration'
| 'Approved'
| 'Rejected'
| 'Disqualified';
| 'Disqualified'
| 'Onboarded';
export interface Application {
id: string;
@ -107,7 +108,12 @@ export interface Application {
architectureDocumentDate?: string;
architectureCompletionDate?: string;
inaugurationDate?: string;
onboardedDate?: string;
questionnaireDate?: string;
shortlistDate?: string;
progressTracking?: any[];
questionnaireResponses?: any[]; // added for response view
stageApprovals?: any[];
participants?: Participant[];
architectureAssignedTo?: string;
architectureStatus?: string;

View File

@ -141,7 +141,6 @@ const PublicQuestionnairePage: React.FC = () => {
const activeQuestions = questions.filter(q => q.sectionName === activeSection);
const currentSectionIndex = sections.indexOf(activeSection);
const totalMarks = questions.reduce((sum, q) => sum + (Number(q.weight) || 0), 0);
return (
<div className="flex-1 flex flex-col overflow-hidden h-screen bg-slate-50">
@ -219,11 +218,6 @@ const PublicQuestionnairePage: React.FC = () => {
<div className="text-amber-400 text-2xl font-bold">{sections.length}</div>
<div className="text-slate-400 text-xs uppercase tracking-wider">Sections</div>
</div>
<div className="h-10 w-px bg-slate-700"></div>
<div className="text-center px-4">
<div className="text-amber-400 text-2xl font-bold">{totalMarks}</div>
<div className="text-slate-400 text-xs uppercase tracking-wider">Total Marks</div>
</div>
</div>
</div>
</div>
@ -282,11 +276,6 @@ const PublicQuestionnairePage: React.FC = () => {
{q.questionText}
{q.isMandatory && <span className="text-red-500 ml-1">*</span>}
</label>
{q.weight > 0 && (
<span className="shrink-0 px-2 py-1 bg-slate-100 text-slate-600 text-xs font-semibold rounded border border-slate-200">
{q.weight} Marks
</span>
)}
</div>
<div className="max-w-xl">

View File

@ -2,200 +2,117 @@ import { API } from '../api/API';
export const onboardingService = {
submitApplication: async (data: any) => {
try {
const response: any = await API.submitApplication(data);
return response.data;
} catch (error) {
console.error('Submit application error:', error);
throw error;
}
const response: any = await API.submitApplication(data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to submit application');
return response.data;
},
getApplications: async () => {
try {
const response: any = await API.getApplications();
return response.data?.data || response.data;
} catch (error) {
console.error('Get applications error:', error);
throw error;
}
const response: any = await API.getApplications();
if (!response.ok) throw new Error(response.data?.message || 'Failed to fetch applications');
return response.data?.data || response.data;
},
shortlistApplications: async (applicationIds: string[], assignedTo: string[], remarks?: string) => {
try {
const response: any = await API.shortlistApplications({ applicationIds, assignedTo, remarks });
return response.data;
} catch (error) {
console.error('Shortlist applications error:', error);
throw error;
}
const response: any = await API.shortlistApplications({ applicationIds, assignedTo, remarks });
if (!response.ok) throw new Error(response.data?.message || 'Failed to shortlist applications');
return response.data;
},
getApplicationById: async (id: string) => {
try {
const response: any = await API.getApplicationById(id);
return response.data?.data || response.data;
} catch (error) {
console.error('Get application by id error:', error);
throw error;
const response: any = await API.getApplicationById(id);
if (!response.ok) {
console.error('API Error Response:', response.status, response.data);
throw new Error(response.data?.message || 'Failed to fetch application details');
}
return response.data?.data || response.data;
},
getUsers: async (params?: any) => {
try {
const response: any = await API.getUsers(params);
return response.data?.data || response.data;
} catch (error) {
console.error('Get users error:', error);
throw error;
}
const response: any = await API.getUsers(params);
if (!response.ok) throw new Error(response.data?.message || 'Failed to fetch users');
return response.data?.data || response.data;
},
addParticipant: async (data: any) => {
try {
const response: any = await API.addParticipant(data);
return response.data;
} catch (error) {
console.error('Add participant error:', error);
throw error;
}
const response: any = await API.addParticipant(data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to add participant');
return response.data;
},
scheduleInterview: async (data: any) => {
try {
const response: any = await API.scheduleInterview(data);
return response.data;
} catch (error) {
console.error('Schedule interview error:', error);
throw error;
}
const response: any = await API.scheduleInterview(data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to schedule interview');
return response.data;
},
updateInterview: async (id: string, data: any) => {
const response: any = await API.updateInterview(id, data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to update interview');
return response.data;
},
getInterviews: async (applicationId: string) => {
try {
const response: any = await API.getInterviews(applicationId);
const data = response.data?.data || response.data;
return Array.isArray(data) ? data : [];
} catch (error) {
console.error('Get interviews error:', error);
throw error;
}
const response: any = await API.getInterviews(applicationId);
if (!response.ok) throw new Error(response.data?.message || 'Failed to fetch interviews');
const data = response.data?.data || response.data;
return Array.isArray(data) ? data : [];
},
getDocuments: async (id: string) => {
try {
const response: any = await API.getDocuments(id);
return response.data?.data || response.data;
} catch (error) {
console.error('Get documents error:', error);
throw error;
}
const response: any = await API.getDocuments(id);
if (!response.ok) throw new Error(response.data?.message || 'Failed to fetch documents');
return response.data?.data || response.data;
},
uploadDocument: async (id: string, data: any) => {
try {
const response: any = await API.uploadDocument(id, data);
return response.data;
} catch (error) {
console.error('Upload document error:', error);
throw error;
}
const response: any = await API.uploadDocument(id, data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to upload document');
return response.data;
},
submitKTMatrix: async (data: any) => {
try {
const response: any = await API.submitKTMatrix(data);
if (response.ok) {
return response.data;
} else {
throw new Error(response.problem || 'Failed to submit KT Matrix');
}
} catch (error) {
console.error('Submit KT Matrix error:', error);
throw error;
}
const response: any = await API.submitKTMatrix(data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to submit KT Matrix');
return response.data;
},
submitLevel2Feedback: async (data: any) => {
try {
const response: any = await API.submitLevel2Feedback(data);
if (response.ok) {
return response.data;
} else {
throw new Error(response.problem || 'Failed to submit Level 2 Feedback');
}
} catch (error) {
console.error('Submit Level 2 Feedback error:', error);
throw error;
}
const response: any = await API.submitLevel2Feedback(data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to submit feedback');
return response.data;
},
updateRecommendation: async (data: any) => {
try {
const response: any = await API.updateRecommendation(data);
if (response.ok) {
return response.data;
} else {
throw new Error(response.problem || 'Failed to update recommendation');
}
} catch (error) {
console.error('Update recommendation error:', error);
throw error;
}
const response: any = await API.updateRecommendation(data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to update recommendation');
return response.data;
},
submitStageDecision: async (data: any) => {
const response: any = await API.submitStageDecision(data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to process stage decision');
return response.data;
},
updateInterviewDecision: async (data: any) => {
try {
const response: any = await API.updateInterviewDecision(data);
if (response.ok) {
return response.data;
} else {
throw new Error(response.problem || 'Failed to update interview decision');
}
} catch (error) {
console.error('Update interview decision error:', error);
throw error;
}
const response: any = await API.updateInterviewDecision(data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to update interview decision');
return response.data;
},
assignArchitectureTeam: async (applicationId: string, assignedTo: string) => {
try {
const response: any = await API.assignArchitectureTeam(applicationId, assignedTo);
return response.data;
} catch (error) {
console.error('Assign architecture team error:', error);
throw error;
}
const response: any = await API.assignArchitectureTeam(applicationId, assignedTo);
if (!response.ok) throw new Error(response.data?.message || 'Failed to assign architecture team');
return response.data;
},
updateArchitectureStatus: async (applicationId: string, status: string, remarks?: string) => {
try {
const response: any = await API.updateArchitectureStatus(applicationId, status, remarks);
return response.data;
} catch (error) {
console.error('Update architecture status error:', error);
throw error;
}
const response: any = await API.updateArchitectureStatus(applicationId, status, remarks);
if (!response.ok) throw new Error(response.data?.message || 'Failed to update architecture status');
return response.data;
},
generateDealerCodes: async (applicationId: string) => {
try {
const response: any = await API.generateDealerCodes(applicationId);
return response.data;
} catch (error) {
console.error('Generate dealer codes error:', error);
throw error;
}
const response: any = await API.generateDealerCodes(applicationId);
if (!response.ok) throw new Error(response.data?.message || 'Failed to generate dealer codes');
return response.data;
},
updateApplicationStatus: async (id: string, data: any) => {
try {
const response: any = await API.updateApplicationStatus(id, data);
return response.data;
} catch (error) {
console.error('Update application status error:', error);
throw error;
}
const response: any = await API.updateApplicationStatus(id, data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to update application status');
return response.data;
},
createDealer: async (data: any) => {
try {
const response: any = await API.createDealer(data);
return response.data;
} catch (error) {
console.error('Create dealer error:', error);
throw error;
}
const response: any = await API.createDealer(data);
if (!response.ok) throw new Error(response.data?.message || 'Failed to create dealer profile');
return response.data;
},
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;
}
const response: any = await API.retriggerEvaluators(id);
if (!response.ok) throw new Error(response.data?.message || 'Failed to retrigger evaluators');
return response.data;
}
};