+
+
updateQuestion(index, 'questionText', e.target.value)}
- className="w-full border p-2 rounded"
- placeholder="Enter question..."
+ className="w-full border border-slate-300 p-2.5 rounded-lg focus:ring-2 focus:ring-amber-500 outline-none transition-shadow"
+ placeholder="Enter your question here..."
/>
-
-
+
+
-
-
+
+
-
-
- updateQuestion(index, 'weight', parseFloat(e.target.value))}
- className="w-full border p-2 rounded"
- />
-
+
+
+
+
+ updateQuestion(index, 'weight', parseFloat(e.target.value))}
+ className="w-full border border-slate-300 p-2.5 rounded-lg focus:ring-2 focus:ring-amber-500 outline-none pl-3 pr-8"
+ title="Weightage"
+ />
+ %
+
+
-
-
updateQuestion(index, 'isMandatory', e.target.checked)}
- className="mr-2"
- />
-
+ {/*
updateQuestion(index, 'isMandatory', !q.isMandatory)}
+ >
+
+
Req.
+
*/}
+
))}
@@ -164,9 +262,9 @@ const QuestionnaireBuilder: React.FC = () => {
);
diff --git a/src/components/admin/QuestionnaireList.tsx b/src/components/admin/QuestionnaireList.tsx
new file mode 100644
index 0000000..77899d2
--- /dev/null
+++ b/src/components/admin/QuestionnaireList.tsx
@@ -0,0 +1,120 @@
+import React, { useState, useEffect } from 'react';
+import { API } from '../../api/API';
+import { toast } from 'sonner';
+import { useNavigate } from 'react-router-dom';
+import { Plus, Edit2, Calendar, CheckCircle, XCircle } from 'lucide-react';
+import { format } from 'date-fns';
+
+interface QuestionnaireVersion {
+ id: string;
+ version: string;
+ isActive: boolean;
+ createdAt: string;
+}
+
+const QuestionnaireList: React.FC = () => {
+ const [versions, setVersions] = useState
([]);
+ const [loading, setLoading] = useState(true);
+ const navigate = useNavigate();
+
+ useEffect(() => {
+ fetchVersions();
+ }, []);
+
+ const fetchVersions = async () => {
+ try {
+ setLoading(true);
+ const response = await API.getAllQuestionnaires() as any;
+ if (response.data?.success) {
+ setVersions(response.data.data);
+ } else {
+ toast.error('Failed to load questionnaire versions');
+ }
+ } catch (error) {
+ console.error(error);
+ toast.error('Error fetching versions');
+ } finally {
+ setLoading(false);
+ }
+ };
+
+ return (
+
+
+
+
Questionnaire Versions
+
Manage your questionnaire templates and versions
+
+
+
+
+ {loading ? (
+
+ ) : versions.length === 0 ? (
+
+
No questionnaire versions found.
+
+
+ ) : (
+
+
+
+
+ | Version Name |
+ Status |
+ Created At |
+ Actions |
+
+
+
+ {versions.map((v) => (
+
+ | {v.version} |
+
+ {v.isActive ? (
+
+ Active
+
+ ) : (
+
+ Inactive
+
+ )}
+ |
+
+
+
+ {format(new Date(v.createdAt), 'MMM dd, yyyy HH:mm')}
+
+ |
+
+
+ |
+
+ ))}
+
+
+
+ )}
+
+ );
+};
+
+export default QuestionnaireList;
diff --git a/src/components/dealer/QuestionnaireForm.tsx b/src/components/dealer/QuestionnaireForm.tsx
index a8d41f2..1a45522 100644
--- a/src/components/dealer/QuestionnaireForm.tsx
+++ b/src/components/dealer/QuestionnaireForm.tsx
@@ -18,16 +18,27 @@ interface QuestionnaireFormProps {
onComplete?: () => void;
readOnly?: boolean;
existingResponses?: any[];
+ publicMode?: boolean; // New prop
+ initialQuestions?: Question[]; // New prop to avoid re-fetching
}
-const QuestionnaireForm: React.FC = ({ applicationId, onComplete, readOnly = false, existingResponses }) => {
- const [questions, setQuestions] = useState([]);
+const QuestionnaireForm: React.FC = ({
+ applicationId,
+ onComplete,
+ readOnly = false,
+ existingResponses,
+ publicMode = false,
+ initialQuestions
+}) => {
+ const [questions, setQuestions] = useState(initialQuestions || []);
const [responses, setResponses] = useState>({});
- const [loading, setLoading] = useState(true);
+ const [loading, setLoading] = useState(!initialQuestions);
const [submitting, setSubmitting] = useState(false);
useEffect(() => {
- fetchQuestionnaire();
+ if (!initialQuestions) {
+ fetchQuestionnaire();
+ }
if (existingResponses) {
const initialResponses: any = {};
existingResponses.forEach(r => {
@@ -35,10 +46,18 @@ const QuestionnaireForm: React.FC = ({ applicationId, on
});
setResponses(initialResponses);
}
- }, [existingResponses]);
+ }, [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);
@@ -70,10 +89,18 @@ const QuestionnaireForm: React.FC = ({ applicationId, on
value: val
}));
- await API.submitQuestionnaireResponse({
- applicationId,
- responses: payload
- });
+ 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) {
diff --git a/src/components/layout/Sidebar.tsx b/src/components/layout/Sidebar.tsx
index 75eb882..edf536f 100644
--- a/src/components/layout/Sidebar.tsx
+++ b/src/components/layout/Sidebar.tsx
@@ -13,7 +13,8 @@ import {
FolderOpen,
Settings,
RefreshCcw,
- MapPin
+ MapPin,
+ ClipboardList
} from 'lucide-react';
import { useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
@@ -92,6 +93,7 @@ export function Sidebar({ onLogout }: SidebarProps) {
if (currentUser?.role === 'Super Admin') {
menuItems.push({ id: 'users', label: 'User Management', icon: Users });
+ menuItems.push({ id: 'questionnaires', label: 'Questionnaire Templates', icon: ClipboardList });
}
const handleSearch = (e: React.FormEvent) => {
diff --git a/src/lib/mock-data.ts b/src/lib/mock-data.ts
index 385055c..347fa2b 100644
--- a/src/lib/mock-data.ts
+++ b/src/lib/mock-data.ts
@@ -191,10 +191,17 @@ export const mockUsers: User[] = [
{
id: '16',
name: 'Yashwin',
- email: 'yashwin@royalenfield.com',
- password: 'password',
+ email: 'yashwin@gmail.com',
+ password: 'Admin@123',
role: 'ZBH',
- }
+ },
+ {
+ id: '17',
+ name: 'Kenil',
+ email: 'kenil@gmail.com',
+ password: 'Admin@123',
+ role: 'DD Lead',
+ },
];
// Mock current user (default)
diff --git a/src/pages/public/PublicQuestionnairePage.tsx b/src/pages/public/PublicQuestionnairePage.tsx
new file mode 100644
index 0000000..5efcf18
--- /dev/null
+++ b/src/pages/public/PublicQuestionnairePage.tsx
@@ -0,0 +1,71 @@
+import React, { useEffect, useState } from 'react';
+import { useParams } from 'react-router-dom';
+import QuestionnaireForm from '../../components/dealer/QuestionnaireForm';
+import { API } from '../../api/API';
+import { toast } from 'sonner';
+
+const PublicQuestionnairePage: React.FC = () => {
+ const { applicationId } = useParams<{ applicationId: string }>();
+ const [isValid, setIsValid] = useState(null);
+ const [appName, setAppName] = useState('');
+ const [questions, setQuestions] = useState([]);
+
+ useEffect(() => {
+ const checkValidity = async () => {
+ if (!applicationId) return;
+ try {
+ // We use the public fetch to verify existence
+ const res = await API.getPublicQuestionnaire(applicationId);
+ if (res.data.success) {
+ setIsValid(true);
+ setAppName(res.data.data.applicationName);
+ setQuestions(res.data.data.questions || []);
+ } else {
+ setIsValid(false);
+ }
+ } catch (error) {
+ setIsValid(false);
+ }
+ };
+ checkValidity();
+ }, [applicationId]);
+
+ if (isValid === null) return Checking application link...
;
+ if (isValid === false) return Invalid or Expired Link
;
+
+ return (
+
+
+
+
Dealer Application Assessment
+
Applicant: {appName}
+
ID: {applicationId}
+
+
+
+
+
+
+
+ Please complete all mandatory fields. You can submit this form only once.
+
+
+
+
+
+
{
+ toast.success("Thank you! Your assessment has been submitted.");
+ setTimeout(() => window.location.reload(), 2000);
+ }}
+ />
+
+
+
+ );
+};
+
+export default PublicQuestionnairePage;