Dealer_Onboard_Frontend/src/components/dealer/QuestionnaireForm.tsx

201 lines
7.8 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { API } from '../../api/API';
import { toast } from 'sonner';
interface Question {
id: string;
sectionName: string;
questionText: string;
inputType: 'text' | 'yesno' | 'file' | 'number';
options?: any;
weight: number;
order: number;
isMandatory: boolean;
}
interface QuestionnaireFormProps {
applicationId: string;
onComplete?: () => void;
readOnly?: boolean;
existingResponses?: any[];
publicMode?: boolean; // New prop
initialQuestions?: Question[]; // New prop to avoid re-fetching
}
const QuestionnaireForm: React.FC<QuestionnaireFormProps> = ({
applicationId,
onComplete,
readOnly = false,
existingResponses,
publicMode = false,
initialQuestions
}) => {
const [questions, setQuestions] = useState<Question[]>(initialQuestions || []);
const [responses, setResponses] = useState<Record<string, any>>({});
const [loading, setLoading] = useState(!initialQuestions);
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
if (!initialQuestions) {
fetchQuestionnaire();
}
if (existingResponses) {
const initialResponses: any = {};
existingResponses.forEach(r => {
initialResponses[r.questionId] = r.responseValue;
});
setResponses(initialResponses);
}
}, [existingResponses, initialQuestions]);
const fetchQuestionnaire = async () => {
try {
// In public mode, we shouldn't fetch "latest" as it requires auth.
// Public page should provide initialQuestions.
// But if we ever needed to fetch, we'd need a public endpoint for just questions or rely on the parent.
if (publicMode) {
setLoading(false);
return;
}
const res = await API.getLatestQuestionnaire();
if (res.data && res.data.data && res.data.data.questions) {
setQuestions(res.data.data.questions);
}
} catch (error) {
console.error(error);
toast.error('Failed to load questionnaire');
} finally {
setLoading(false);
}
};
const handleInputChange = (questionId: string, value: any) => {
if (readOnly) return;
setResponses(prev => ({ ...prev, [questionId]: value }));
};
const handleSubmit = async () => {
const missing = questions.filter(q => q.isMandatory && !responses[q.id]);
if (missing.length > 0) {
toast.error(`Please answer all mandatory questions. Missing: ${missing.length}`);
return;
}
try {
setSubmitting(true);
const payload = Object.entries(responses).map(([qId, val]) => ({
questionId: qId,
value: val
}));
if (publicMode) {
await API.submitPublicResponse({
applicationId,
responses: payload
});
} else {
await API.submitQuestionnaireResponse({
applicationId,
responses: payload
});
}
toast.success('Responses submitted successfully');
if (onComplete) onComplete();
} catch (error) {
console.error(error);
toast.error('Failed to submit responses');
} finally {
setSubmitting(false);
}
};
if (loading) return <div>Loading questionnaire...</div>;
if (questions.length === 0) return <div>No active questionnaire found.</div>;
const sections = questions.reduce((acc, q) => {
if (!acc[q.sectionName]) acc[q.sectionName] = [];
acc[q.sectionName].push(q);
return acc;
}, {} as Record<string, Question[]>);
return (
<div className="space-y-6">
<h3 className="text-xl font-semibold">Dealership Assessment Questionnaire</h3>
{Object.entries(sections).map(([sectionName, sectionQuestions]) => (
<div key={sectionName} className="border p-4 rounded bg-white shadow-sm">
<h4 className="font-medium text-lg mb-4 border-b pb-2">{sectionName}</h4>
<div className="space-y-4">
{sectionQuestions.map(q => (
<div key={q.id}>
<label className="block text-sm font-medium mb-1">
{q.questionText} {q.isMandatory && !readOnly && <span className="text-red-500">*</span>}
</label>
{q.inputType === 'text' && (
<input
type="text"
className="w-full border p-2 rounded disabled:bg-gray-100"
onChange={(e) => handleInputChange(q.id, e.target.value)}
value={responses[q.id] || ''}
disabled={readOnly}
/>
)}
{q.inputType === 'number' && (
<input
type="number"
className="w-full border p-2 rounded disabled:bg-gray-100"
onChange={(e) => handleInputChange(q.id, e.target.value)}
value={responses[q.id] || ''}
disabled={readOnly}
/>
)}
{q.inputType === 'yesno' && (
<div className="flex gap-4">
<label className="flex items-center gap-2">
<input
type="radio"
name={`q-${q.id}`}
value="yes"
checked={responses[q.id] === 'yes'}
onChange={() => handleInputChange(q.id, 'yes')}
disabled={readOnly}
/> Yes
</label>
<label className="flex items-center gap-2">
<input
type="radio"
name={`q-${q.id}`}
value="no"
checked={responses[q.id] === 'no'}
onChange={() => handleInputChange(q.id, 'no')}
disabled={readOnly}
/> No
</label>
</div>
)}
</div>
))}
</div>
</div>
))}
{!readOnly && (
<button
onClick={handleSubmit}
disabled={submitting}
className="bg-blue-600 text-white px-6 py-2 rounded hover:bg-blue-700 w-full md:w-auto"
>
{submitting ? 'Submitting...' : 'Submit Assessment'}
</button>
)}
</div>
);
};
export default QuestionnaireForm;