feat(19-03): create QuestionForm component
- Renders mixed question types from questions array - Submit disabled until all questions answered - onSubmit called with Record<string, string> mapping questionId to answer
This commit is contained in:
91
packages/web/src/components/QuestionForm.tsx
Normal file
91
packages/web/src/components/QuestionForm.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { useState } from "react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { OptionGroup } from "@/components/OptionGroup";
|
||||
import { FreeTextInput } from "@/components/FreeTextInput";
|
||||
|
||||
interface QuestionFormQuestion {
|
||||
id: string;
|
||||
question: string;
|
||||
options?: Array<{ label: string; description?: string }>;
|
||||
multiSelect?: boolean;
|
||||
}
|
||||
|
||||
interface QuestionFormProps {
|
||||
questions: QuestionFormQuestion[];
|
||||
onSubmit: (answers: Record<string, string>) => void;
|
||||
onCancel: () => void;
|
||||
isSubmitting?: boolean;
|
||||
}
|
||||
|
||||
export function QuestionForm({
|
||||
questions,
|
||||
onSubmit,
|
||||
onCancel,
|
||||
isSubmitting = false,
|
||||
}: QuestionFormProps) {
|
||||
const [answers, setAnswers] = useState<Record<string, string>>(() => {
|
||||
const initial: Record<string, string> = {};
|
||||
for (const q of questions) {
|
||||
initial[q.id] = "";
|
||||
}
|
||||
return initial;
|
||||
});
|
||||
|
||||
function handleAnswerChange(questionId: string, value: string) {
|
||||
setAnswers((prev) => ({ ...prev, [questionId]: value }));
|
||||
}
|
||||
|
||||
const allAnswered = questions.every(
|
||||
(q) => answers[q.id] !== undefined && answers[q.id].trim() !== ""
|
||||
);
|
||||
|
||||
function handleSubmit() {
|
||||
if (allAnswered) {
|
||||
onSubmit(answers);
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{questions.map((q, index) => (
|
||||
<div key={q.id} className="space-y-2">
|
||||
<p className="text-sm font-medium">
|
||||
Q{index + 1}: {q.question}
|
||||
</p>
|
||||
|
||||
{q.options && q.options.length > 0 ? (
|
||||
<OptionGroup
|
||||
questionId={q.id}
|
||||
options={q.options}
|
||||
multiSelect={q.multiSelect ?? false}
|
||||
value={answers[q.id] ?? ""}
|
||||
onChange={(value) => handleAnswerChange(q.id, value)}
|
||||
/>
|
||||
) : (
|
||||
<FreeTextInput
|
||||
questionId={q.id}
|
||||
value={answers[q.id] ?? ""}
|
||||
onChange={(value) => handleAnswerChange(q.id, value)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className="flex justify-end gap-2 pt-2">
|
||||
<Button
|
||||
variant="outline"
|
||||
onClick={onCancel}
|
||||
disabled={isSubmitting}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleSubmit}
|
||||
disabled={!allAnswered || isSubmitting}
|
||||
>
|
||||
{isSubmitting ? "Sending..." : "Send Answers"}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user