enhanced questionnarier and request detail screen
This commit is contained in:
parent
8ef092f723
commit
0ff5412d04
@ -39,6 +39,18 @@ export const API = {
|
|||||||
getPublicQuestionnaire: (appId: string) => axios.get(`http://localhost:5000/api/questionnaire/public/${appId}`), // Direct axios to bypass interceptors if client has auth
|
getPublicQuestionnaire: (appId: string) => axios.get(`http://localhost:5000/api/questionnaire/public/${appId}`), // Direct axios to bypass interceptors if client has auth
|
||||||
submitPublicResponse: (data: any) => axios.post('http://localhost:5000/api/questionnaire/public/submit', data),
|
submitPublicResponse: (data: any) => axios.post('http://localhost:5000/api/questionnaire/public/submit', data),
|
||||||
|
|
||||||
|
// Assessment & Interviews
|
||||||
|
getAiSummary: (appId: string) => client.get(`/assessment/ai-summary/${appId}`),
|
||||||
|
scheduleInterview: (data: any) => client.post('/assessment/interviews', data),
|
||||||
|
updateInterview: (id: string, data: any) => client.put(`/assessment/interviews/${id}`, data),
|
||||||
|
submitEvaluation: (id: string, data: any) => client.post(`/assessment/interviews/${id}/evaluation`, data),
|
||||||
|
|
||||||
|
// Collaboration & Participants
|
||||||
|
getWorknotes: (requestId: string, requestType: string) => client.get('/collaboration/worknotes', { params: { requestId, requestType } }),
|
||||||
|
addWorknote: (data: any) => client.post('/collaboration/worknotes', data),
|
||||||
|
addParticipant: (data: any) => client.post('/collaboration/participants', data),
|
||||||
|
removeParticipant: (id: string) => client.delete(`/collaboration/participants/${id}`),
|
||||||
|
|
||||||
// User management routes
|
// User management routes
|
||||||
getUsers: () => client.get('/admin/users'),
|
getUsers: () => client.get('/admin/users'),
|
||||||
createUser: (data: any) => client.post('/admin/users', data),
|
createUser: (data: any) => client.post('/admin/users', data),
|
||||||
|
|||||||
@ -37,7 +37,7 @@ 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 { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } 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 {
|
||||||
Table,
|
Table,
|
||||||
@ -152,6 +152,7 @@ export function ApplicationDetails() {
|
|||||||
loaDate: getStageDate('LOA'),
|
loaDate: getStageDate('LOA'),
|
||||||
eorCompleteDate: getStageDate('EOR Complete'),
|
eorCompleteDate: getStageDate('EOR Complete'),
|
||||||
inaugurationDate: getStageDate('Inauguration'),
|
inaugurationDate: getStageDate('Inauguration'),
|
||||||
|
participants: data.participants || [],
|
||||||
};
|
};
|
||||||
setApplication(mappedApp);
|
setApplication(mappedApp);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -175,6 +176,7 @@ export function ApplicationDetails() {
|
|||||||
const [showLevel2FeedbackModal, setShowLevel2FeedbackModal] = useState(false);
|
const [showLevel2FeedbackModal, setShowLevel2FeedbackModal] = useState(false);
|
||||||
const [showLevel3FeedbackModal, setShowLevel3FeedbackModal] = useState(false);
|
const [showLevel3FeedbackModal, setShowLevel3FeedbackModal] = useState(false);
|
||||||
const [showDocumentsModal, setShowDocumentsModal] = useState(false);
|
const [showDocumentsModal, setShowDocumentsModal] = useState(false);
|
||||||
|
const [showAssignModal, setShowAssignModal] = useState(false);
|
||||||
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('');
|
||||||
@ -184,6 +186,25 @@ export function ApplicationDetails() {
|
|||||||
'architectural-work': true,
|
'architectural-work': true,
|
||||||
'statutory-documents': true
|
'statutory-documents': true
|
||||||
});
|
});
|
||||||
|
const [users, setUsers] = useState<any[]>([]);
|
||||||
|
const [selectedUser, setSelectedUser] = useState<string>('');
|
||||||
|
const [participantType, setParticipantType] = useState<string>('contributor');
|
||||||
|
const [interviewDate, setInterviewDate] = useState('');
|
||||||
|
const [interviewType, setInterviewType] = useState('level1');
|
||||||
|
const [meetingLink, setMeetingLink] = useState('');
|
||||||
|
const [location, setLocation] = useState('');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchUsers = async () => {
|
||||||
|
try {
|
||||||
|
const response = await onboardingService.getUsers();
|
||||||
|
setUsers(response || []);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to fetch users', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
fetchUsers();
|
||||||
|
}, []);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return <div>Loading application details...</div>;
|
return <div>Loading application details...</div>;
|
||||||
@ -575,6 +596,50 @@ export function ApplicationDetails() {
|
|||||||
setWorkNote('');
|
setWorkNote('');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleAddParticipant = async () => {
|
||||||
|
if (!selectedUser) {
|
||||||
|
alert('Please select a user');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await onboardingService.addParticipant({
|
||||||
|
requestId: applicationId,
|
||||||
|
requestType: 'application',
|
||||||
|
userId: selectedUser,
|
||||||
|
participantType
|
||||||
|
});
|
||||||
|
alert('User assigned successfully!');
|
||||||
|
// Refresh application data
|
||||||
|
const data = await onboardingService.getApplicationById(applicationId);
|
||||||
|
setApplication({ ...application, participants: data.participants || [] });
|
||||||
|
setSelectedUser('');
|
||||||
|
setShowAssignModal(false);
|
||||||
|
} catch (error) {
|
||||||
|
alert('Failed to assign user');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleScheduleInterview = async () => {
|
||||||
|
if (!interviewDate) {
|
||||||
|
alert('Please select date and time');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await onboardingService.scheduleInterview({
|
||||||
|
applicationId,
|
||||||
|
level: interviewType,
|
||||||
|
scheduledAt: interviewDate,
|
||||||
|
type: interviewMode,
|
||||||
|
location: interviewMode === 'virtual' ? meetingLink : location,
|
||||||
|
participants: [] // Optional: add multi-participant support if needed
|
||||||
|
});
|
||||||
|
alert('Interview scheduled successfully!');
|
||||||
|
setShowScheduleModal(false);
|
||||||
|
} catch (error) {
|
||||||
|
alert('Failed to schedule interview');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// If Work Notes page is open, show that instead
|
// If Work Notes page is open, show that instead
|
||||||
if (showWorkNotesPage) {
|
if (showWorkNotesPage) {
|
||||||
return (
|
return (
|
||||||
@ -584,6 +649,7 @@ export function ApplicationDetails() {
|
|||||||
registrationNumber={application.registrationNumber}
|
registrationNumber={application.registrationNumber}
|
||||||
onBack={() => setShowWorkNotesPage(false)}
|
onBack={() => setShowWorkNotesPage(false)}
|
||||||
initialNotes={mockWorkNotes}
|
initialNotes={mockWorkNotes}
|
||||||
|
participants={application.participants}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1232,10 +1298,55 @@ export function ApplicationDetails() {
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Button variant="outline" className="w-full">
|
<Dialog open={showAssignModal} onOpenChange={setShowAssignModal}>
|
||||||
<User className="w-4 h-4 mr-2" />
|
<DialogTrigger asChild>
|
||||||
Assign User
|
<Button variant="outline" className="w-full">
|
||||||
</Button>
|
<User className="w-4 h-4 mr-2" />
|
||||||
|
Assign User
|
||||||
|
</Button>
|
||||||
|
</DialogTrigger>
|
||||||
|
<DialogContent>
|
||||||
|
<DialogHeader>
|
||||||
|
<DialogTitle>Assign User to Application</DialogTitle>
|
||||||
|
<DialogDescription>
|
||||||
|
Select a user and their role for this application.
|
||||||
|
</DialogDescription>
|
||||||
|
</DialogHeader>
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div>
|
||||||
|
<Label>Select User</Label>
|
||||||
|
<Select value={selectedUser} onValueChange={setSelectedUser}>
|
||||||
|
<SelectTrigger className="mt-2">
|
||||||
|
<SelectValue placeholder="Search users..." />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
{users.map((u) => (
|
||||||
|
<SelectItem key={u.id} value={u.id}>
|
||||||
|
{u.fullName} ({u.email})
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<Label>Assignment Role</Label>
|
||||||
|
<Select value={participantType} onValueChange={setParticipantType}>
|
||||||
|
<SelectTrigger className="mt-2">
|
||||||
|
<SelectValue placeholder="Select role" />
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="owner">Owner</SelectItem>
|
||||||
|
<SelectItem value="contributor">Contributor</SelectItem>
|
||||||
|
<SelectItem value="reviewer">Reviewer</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<Button className="w-full bg-amber-600 hover:bg-amber-700" onClick={handleAddParticipant}>
|
||||||
|
Confirm Assignment
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
)}
|
)}
|
||||||
@ -1411,7 +1522,7 @@ export function ApplicationDetails() {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<Label>Interview Type</Label>
|
<Label>Interview Type</Label>
|
||||||
<Select>
|
<Select value={interviewType} onValueChange={setInterviewType}>
|
||||||
<SelectTrigger className="mt-2">
|
<SelectTrigger className="mt-2">
|
||||||
<SelectValue placeholder="Select interview type" />
|
<SelectValue placeholder="Select interview type" />
|
||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
@ -1436,22 +1547,33 @@ export function ApplicationDetails() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Label>Date & Time</Label>
|
<Label>Date & Time</Label>
|
||||||
<Input type="datetime-local" className="mt-2" />
|
<Input
|
||||||
</div>
|
type="datetime-local"
|
||||||
<div>
|
className="mt-2"
|
||||||
<Label>Participants</Label>
|
value={interviewDate}
|
||||||
<Input placeholder="Enter participant emails (comma-separated)" className="mt-2" />
|
onChange={(e) => setInterviewDate(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
{interviewMode === 'virtual' && (
|
{interviewMode === 'virtual' && (
|
||||||
<div>
|
<div>
|
||||||
<Label>Meeting Link</Label>
|
<Label>Meeting Link</Label>
|
||||||
<Input placeholder="https://meet.google.com/..." className="mt-2" />
|
<Input
|
||||||
|
placeholder="https://meet.google.com/..."
|
||||||
|
className="mt-2"
|
||||||
|
value={meetingLink}
|
||||||
|
onChange={(e) => setMeetingLink(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{interviewMode === 'physical' && (
|
{interviewMode === 'physical' && (
|
||||||
<div>
|
<div>
|
||||||
<Label>Location</Label>
|
<Label>Location</Label>
|
||||||
<Input placeholder="Enter interview location address" className="mt-2" />
|
<Input
|
||||||
|
placeholder="Enter interview location address"
|
||||||
|
className="mt-2"
|
||||||
|
value={location}
|
||||||
|
onChange={(e) => setLocation(e.target.value)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div className="flex gap-3">
|
<div className="flex gap-3">
|
||||||
@ -1464,10 +1586,7 @@ 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={() => {
|
onClick={handleScheduleInterview}
|
||||||
alert('Interview scheduled! Calendar invites will be sent.');
|
|
||||||
setShowScheduleModal(false);
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
Schedule
|
Schedule
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@ -12,7 +12,7 @@ import {
|
|||||||
Image as ImageIcon,
|
Image as ImageIcon,
|
||||||
MessageSquare
|
MessageSquare
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { WorkNote } from '../../lib/mock-data';
|
import { WorkNote, Participant } from '../../lib/mock-data';
|
||||||
|
|
||||||
interface WorkNotesPageProps {
|
interface WorkNotesPageProps {
|
||||||
applicationId: string;
|
applicationId: string;
|
||||||
@ -20,9 +20,11 @@ interface WorkNotesPageProps {
|
|||||||
registrationNumber: string;
|
registrationNumber: string;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
initialNotes?: WorkNote[];
|
initialNotes?: WorkNote[];
|
||||||
|
participants?: Participant[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Participant {
|
// This interface defines the structure for participants displayed in the UI
|
||||||
|
interface ParticipantUI {
|
||||||
name: string;
|
name: string;
|
||||||
initials: string;
|
initials: string;
|
||||||
color: string;
|
color: string;
|
||||||
@ -33,7 +35,8 @@ export function WorkNotesPage({
|
|||||||
applicationName,
|
applicationName,
|
||||||
registrationNumber,
|
registrationNumber,
|
||||||
onBack,
|
onBack,
|
||||||
initialNotes = []
|
initialNotes = [],
|
||||||
|
participants: externalParticipants = []
|
||||||
}: WorkNotesPageProps) {
|
}: WorkNotesPageProps) {
|
||||||
const [notes, setNotes] = useState<WorkNote[]>(initialNotes);
|
const [notes, setNotes] = useState<WorkNote[]>(initialNotes);
|
||||||
const [message, setMessage] = useState('');
|
const [message, setMessage] = useState('');
|
||||||
@ -43,13 +46,40 @@ export function WorkNotesPage({
|
|||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
const scrollRef = useRef<HTMLDivElement>(null);
|
const scrollRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
// Mock participants for @mentions
|
const getInitials = (name: string) => {
|
||||||
const participants: Participant[] = [
|
return name
|
||||||
{ name: 'Sarah Chen', initials: 'SC', color: 'bg-green-600' },
|
.split(' ')
|
||||||
{ name: 'Lisa Wong', initials: 'LW', color: 'bg-blue-600' },
|
.map(n => n[0])
|
||||||
{ name: 'Mark Johnson', initials: 'MJ', color: 'bg-purple-600' },
|
.join('')
|
||||||
{ name: 'Anjali Sharma', initials: 'AS', color: 'bg-amber-600' },
|
.toUpperCase()
|
||||||
];
|
.substring(0, 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getAvatarColor = (name: string) => {
|
||||||
|
const colors = [
|
||||||
|
'bg-green-600',
|
||||||
|
'bg-blue-600',
|
||||||
|
'bg-purple-600',
|
||||||
|
'bg-amber-600',
|
||||||
|
'bg-pink-600',
|
||||||
|
'bg-indigo-600',
|
||||||
|
'bg-teal-600',
|
||||||
|
];
|
||||||
|
const index = name.length % colors.length;
|
||||||
|
return colors[index];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Map backend participants to the UI sub-format
|
||||||
|
const participantsList: ParticipantUI[] = externalParticipants.map(p => ({
|
||||||
|
name: p.user?.name || 'Unknown User',
|
||||||
|
initials: getInitials(p.user?.name || 'U'),
|
||||||
|
color: getAvatarColor(p.user?.name || 'U')
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Fallback to current user if no participants
|
||||||
|
if (participantsList.length === 0) {
|
||||||
|
participantsList.push({ name: 'System User', initials: 'SU', color: 'bg-slate-600' });
|
||||||
|
}
|
||||||
|
|
||||||
// Scroll to bottom when new messages arrive
|
// Scroll to bottom when new messages arrive
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -152,30 +182,8 @@ export function WorkNotesPage({
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const getInitials = (name: string) => {
|
|
||||||
return name
|
|
||||||
.split(' ')
|
|
||||||
.map(n => n[0])
|
|
||||||
.join('')
|
|
||||||
.toUpperCase()
|
|
||||||
.substring(0, 2);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getAvatarColor = (name: string) => {
|
const filteredParticipants = participantsList.filter(p =>
|
||||||
const colors = [
|
|
||||||
'bg-green-600',
|
|
||||||
'bg-blue-600',
|
|
||||||
'bg-purple-600',
|
|
||||||
'bg-amber-600',
|
|
||||||
'bg-pink-600',
|
|
||||||
'bg-indigo-600',
|
|
||||||
'bg-teal-600',
|
|
||||||
];
|
|
||||||
const index = name.length % colors.length;
|
|
||||||
return colors[index];
|
|
||||||
};
|
|
||||||
|
|
||||||
const filteredParticipants = participants.filter(p =>
|
|
||||||
p.name.toLowerCase().includes(mentionQuery.toLowerCase())
|
p.name.toLowerCase().includes(mentionQuery.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -210,7 +218,7 @@ export function WorkNotesPage({
|
|||||||
|
|
||||||
{/* Participant Avatars */}
|
{/* Participant Avatars */}
|
||||||
<div className="flex items-center -space-x-2">
|
<div className="flex items-center -space-x-2">
|
||||||
{participants.slice(0, 3).map((participant, index) => (
|
{participantsList.slice(0, 3).map((participant, index) => (
|
||||||
<Avatar
|
<Avatar
|
||||||
key={index}
|
key={index}
|
||||||
className="w-8 h-8 border-2 border-white"
|
className="w-8 h-8 border-2 border-white"
|
||||||
@ -220,9 +228,9 @@ export function WorkNotesPage({
|
|||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
))}
|
))}
|
||||||
{participants.length > 3 && (
|
{participantsList.length > 3 && (
|
||||||
<div className="w-8 h-8 rounded-full bg-slate-200 border-2 border-white flex items-center justify-center">
|
<div className="w-8 h-8 rounded-full bg-slate-200 border-2 border-white flex items-center justify-center">
|
||||||
<span className="text-slate-600 text-xs">+{participants.length - 3}</span>
|
<span className="text-slate-600 text-xs">+{participantsList.length - 3}</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -6,7 +6,7 @@ interface Question {
|
|||||||
id: string;
|
id: string;
|
||||||
sectionName: string;
|
sectionName: string;
|
||||||
questionText: string;
|
questionText: string;
|
||||||
inputType: 'text' | 'yesno' | 'file' | 'number' | 'select' | 'mcq';
|
inputType: 'text' | 'yesno' | 'file' | 'number' | 'select' | 'mcq' | 'radio' | 'textarea' | 'email';
|
||||||
options?: any;
|
options?: any;
|
||||||
questionOptions?: any[]; // From backend inclusion
|
questionOptions?: any[]; // From backend inclusion
|
||||||
weight: number;
|
weight: number;
|
||||||
@ -158,9 +158,9 @@ const QuestionnaireForm: React.FC<QuestionnaireFormProps> = ({
|
|||||||
{q.questionText} {q.isMandatory && !readOnly && <span className="text-red-500">*</span>}
|
{q.questionText} {q.isMandatory && !readOnly && <span className="text-red-500">*</span>}
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
{q.inputType === 'text' && (
|
{(q.inputType === 'text' || q.inputType === 'email') && (
|
||||||
<input
|
<input
|
||||||
type="text"
|
type={q.inputType === 'email' ? 'email' : 'text'}
|
||||||
className="w-full border p-2 rounded disabled:bg-gray-100"
|
className="w-full border p-2 rounded disabled:bg-gray-100"
|
||||||
onChange={(e) => handleInputChange(q.id, e.target.value)}
|
onChange={(e) => handleInputChange(q.id, e.target.value)}
|
||||||
value={responses[q.id] || ''}
|
value={responses[q.id] || ''}
|
||||||
@ -168,6 +168,15 @@ const QuestionnaireForm: React.FC<QuestionnaireFormProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{q.inputType === 'textarea' && (
|
||||||
|
<textarea
|
||||||
|
className="w-full border p-2 rounded disabled:bg-gray-100 min-h-[100px]"
|
||||||
|
onChange={(e) => handleInputChange(q.id, e.target.value)}
|
||||||
|
value={responses[q.id] || ''}
|
||||||
|
disabled={readOnly}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{q.inputType === 'file' && (
|
{q.inputType === 'file' && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<input
|
<input
|
||||||
@ -202,7 +211,7 @@ const QuestionnaireForm: React.FC<QuestionnaireFormProps> = ({
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(q.inputType === 'yesno' || q.inputType === 'select' || q.inputType === 'mcq') && (
|
{(q.inputType === 'yesno' || q.inputType === 'select' || q.inputType === 'mcq' || q.inputType === 'radio') && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
{/* Use backend options if available, or fallbacks for legacy yesno */}
|
{/* Use backend options if available, or fallbacks for legacy yesno */}
|
||||||
{(q.questionOptions && q.questionOptions.length > 0 ? q.questionOptions : (
|
{(q.questionOptions && q.questionOptions.length > 0 ? q.questionOptions : (
|
||||||
|
|||||||
@ -103,6 +103,19 @@ export interface Application {
|
|||||||
architectureCompletionDate?: string;
|
architectureCompletionDate?: string;
|
||||||
inaugurationDate?: string;
|
inaugurationDate?: string;
|
||||||
questionnaireResponses?: any[]; // added for response view
|
questionnaireResponses?: any[]; // added for response view
|
||||||
|
participants?: Participant[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Participant {
|
||||||
|
id: string;
|
||||||
|
userId: string;
|
||||||
|
participantType: string;
|
||||||
|
joinedMethod: string;
|
||||||
|
user?: {
|
||||||
|
name: string;
|
||||||
|
email: string;
|
||||||
|
role: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
|
|||||||
@ -36,5 +36,32 @@ export const onboardingService = {
|
|||||||
console.error('Get application by id error:', error);
|
console.error('Get application by id error:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
getUsers: async () => {
|
||||||
|
try {
|
||||||
|
const response = await API.getUsers();
|
||||||
|
return response.data?.data || response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Get users error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
addParticipant: async (data: any) => {
|
||||||
|
try {
|
||||||
|
const response = await API.addParticipant(data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Add participant error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
scheduleInterview: async (data: any) => {
|
||||||
|
try {
|
||||||
|
const response = await API.scheduleInterview(data);
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Schedule interview error:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user