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