Skip to content

Commit ff3e177

Browse files
feat: add advanced mode per question (#131)
* feat: dodanie trybu zaawansowanego per-pytanie * feat: added advanced mode per question fix * fix: correct some mistakes * fix: correct some mistakes --------- Co-authored-by: Antoni Czaplicki <56671347+Antoni-Czaplicki@users.noreply.github.com> Co-authored-by: Antoni Czaplicki <antekczaplicki@gmail.com>
1 parent 7e79134 commit ff3e177

File tree

2 files changed

+106
-80
lines changed

2 files changed

+106
-80
lines changed

src/components/quiz/question-form.tsx

Lines changed: 50 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -5,22 +5,23 @@ import { Checkbox } from "@/components/ui/checkbox";
55
import { Input } from "@/components/ui/input";
66
import { Label } from "@/components/ui/label";
77
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
8+
import { Switch } from "@/components/ui/switch";
89
import { Textarea } from "@/components/ui/textarea";
910
import type { Answer, Question } from "@/types/quiz.ts";
1011

11-
interface questionFormProps {
12-
question: Question;
13-
onUpdate: (updatedQuestion: Question) => void;
12+
interface QuestionFormProps {
13+
question: Question & { advanced?: boolean };
14+
onUpdate: (updatedQuestion: Question & { advanced?: boolean }) => void;
1415
onRemove: (id: number) => void;
15-
advancedMode?: boolean;
1616
}
1717

1818
export function QuestionForm({
1919
question,
2020
onUpdate,
2121
onRemove,
22-
advancedMode = false,
23-
}: questionFormProps) {
22+
}: QuestionFormProps) {
23+
const isAdvanced = Boolean(question.advanced);
24+
2425
const handleTextChange = (text: string) => {
2526
onUpdate({ ...question, question: text });
2627
};
@@ -95,16 +96,30 @@ export function QuestionForm({
9596
>
9697
Pytanie {question.id}
9798
</Label>
98-
<Button
99-
variant="ghost"
100-
size="icon"
101-
className="text-muted-foreground hover:bg-destructive/10 hover:text-destructive h-7 w-7 shrink-0 rounded-full transition"
102-
onClick={() => {
103-
onRemove(question.id);
104-
}}
105-
>
106-
<Trash2 className="size-4" />
107-
</Button>
99+
<div className="flex items-center gap-2">
100+
<div className="flex items-center gap-1">
101+
<Switch
102+
id={`advanced-question-${question.id.toString()}`}
103+
checked={isAdvanced}
104+
onCheckedChange={(checked) => {
105+
onUpdate({ ...question, advanced: checked });
106+
}}
107+
/>
108+
<span className="text-muted-foreground text-xs">
109+
Zaawansowane
110+
</span>
111+
</div>
112+
<Button
113+
variant="ghost"
114+
size="icon"
115+
className="text-muted-foreground hover:bg-destructive/10 hover:text-destructive h-7 w-7 shrink-0 rounded-full transition"
116+
onClick={() => {
117+
onRemove(question.id);
118+
}}
119+
>
120+
<Trash2 className="size-4" />
121+
</Button>
122+
</div>
108123
</div>
109124
<Textarea
110125
id={`question-text-${question.id.toString()}`}
@@ -116,7 +131,7 @@ export function QuestionForm({
116131
/>
117132
</div>
118133

119-
{advancedMode ? (
134+
{isAdvanced ? (
120135
<div className="space-y-4">
121136
<div className="flex flex-col gap-4">
122137
<div className="space-y-2">
@@ -145,25 +160,26 @@ export function QuestionForm({
145160
}}
146161
/>
147162
</div>
148-
<div className="flex items-center gap-2">
149-
<Checkbox
150-
id={`multiple-choice-${question.id.toString()}`}
151-
checked={question.multiple}
152-
onCheckedChange={(checked) => {
153-
handleMultipleChange(Boolean(checked));
154-
}}
155-
/>
156-
<Label
157-
htmlFor={`multiple-choice-${question.id.toString()}`}
158-
className="cursor-pointer"
159-
>
160-
Wielokrotny wybór (można zaznaczyć więcej niż jedną odpowiedź)
161-
</Label>
162-
</div>
163163
</div>
164164
</div>
165165
) : null}
166166

167+
<div className="flex items-center gap-2">
168+
<Checkbox
169+
id={`multiple-choice-${question.id.toString()}`}
170+
checked={question.multiple}
171+
onCheckedChange={(checked) => {
172+
handleMultipleChange(Boolean(checked));
173+
}}
174+
/>
175+
<Label
176+
htmlFor={`multiple-choice-${question.id.toString()}`}
177+
className="cursor-pointer"
178+
>
179+
Wielokrotny wybór
180+
</Label>
181+
</div>
182+
167183
<h6 className="text-sm font-semibold tracking-tight">
168184
Odpowiedzi
169185
<span className="text-muted-foreground ml-1 font-normal">
@@ -188,7 +204,7 @@ export function QuestionForm({
188204
});
189205
}}
190206
/>
191-
{advancedMode ? (
207+
{isAdvanced ? (
192208
<Input
193209
placeholder="URL zdjęcia dla odpowiedzi"
194210
value={answer.image ?? ""}
@@ -261,7 +277,7 @@ export function QuestionForm({
261277
});
262278
}}
263279
/>
264-
{advancedMode ? (
280+
{isAdvanced ? (
265281
<Input
266282
placeholder="URL zdjęcia dla odpowiedzi"
267283
value={answer.image ?? ""}

src/components/quiz/quiz-editor.tsx

Lines changed: 56 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -34,17 +34,22 @@ interface QuizEditorProps {
3434
saving?: boolean;
3535
}
3636

37-
// Utility to strip advanced fields when advancedMode is disabled
38-
const sanitizeQuestions = (questions: Question[], advancedMode: boolean) =>
39-
questions.map((q) => ({
40-
...q,
41-
image: advancedMode ? q.image : undefined,
42-
explanation: advancedMode ? q.explanation : undefined,
43-
answers: q.answers.map((a) => ({
44-
...a,
45-
image: advancedMode ? a.image : undefined,
46-
})),
47-
}));
37+
type QuestionWithAdvanced = Question & { advanced?: boolean };
38+
39+
const sanitizeQuestions = (questions: QuestionWithAdvanced[]) =>
40+
questions.map((q) => {
41+
const isAdvanced = Boolean(q.advanced);
42+
const { advanced, ...rest } = q;
43+
return {
44+
...rest,
45+
image: isAdvanced ? q.image : undefined,
46+
explanation: isAdvanced ? q.explanation : undefined,
47+
answers: q.answers.map((a) => ({
48+
...a,
49+
image: isAdvanced ? a.image : undefined,
50+
})),
51+
};
52+
});
4853

4954
const scrollToBottom = () => {
5055
window.scrollTo({ top: document.body.scrollHeight, behavior: "smooth" });
@@ -61,13 +66,28 @@ export function QuizEditor({
6166
onSaveAndClose,
6267
saving = false,
6368
}: QuizEditorProps) {
69+
const initialAdvancedDefault =
70+
initialQuiz?.questions?.some(
71+
(q) =>
72+
Boolean(q.image) ||
73+
Boolean(q.explanation) ||
74+
q.answers.some((a) => Boolean(a.image)),
75+
) ?? false;
76+
6477
const [title, setTitle] = useState(initialQuiz?.title ?? "");
6578
const [description, setDescription] = useState(
6679
initialQuiz?.description ?? "",
6780
);
68-
const [questions, setQuestions] = useState<Question[]>(() => {
81+
82+
const [questions, setQuestions] = useState<QuestionWithAdvanced[]>(() => {
6983
if (initialQuiz?.questions != null && initialQuiz.questions.length > 0) {
70-
return initialQuiz.questions;
84+
return initialQuiz.questions.map((q) => ({
85+
...q,
86+
advanced:
87+
Boolean(q.image) ||
88+
Boolean(q.explanation) ||
89+
q.answers.some((a) => Boolean(a.image)),
90+
}));
7191
}
7292
return [
7393
{
@@ -78,23 +98,20 @@ export function QuizEditor({
7898
{ answer: "", correct: false },
7999
{ answer: "", correct: false },
80100
],
101+
image: "",
102+
explanation: "",
103+
advanced: initialAdvancedDefault,
81104
},
82105
];
83106
});
107+
84108
const [error, setError] = useState<string | null>(null);
85-
const [advancedMode, setAdvancedMode] = useState(
86-
initialQuiz?.questions?.some(
87-
(q) =>
88-
Boolean(q.image) ||
89-
Boolean(q.explanation) ||
90-
q.answers.some((a) => Boolean(a.image)),
91-
) ?? false,
92-
);
109+
const [advancedMode, setAdvancedMode] = useState(initialAdvancedDefault);
110+
93111
const [previousQuestionId, setPreviousQuestionId] = useState<number>(() =>
94112
questions.reduce((max, q) => Math.max(q.id, max), 0),
95113
);
96114

97-
// all questions multiple toggle state (true / false / mixed null)
98115
const allQuestionsMultiple: boolean | null = useMemo(() => {
99116
if (questions.length === 0) {
100117
return null;
@@ -128,6 +145,7 @@ export function QuizEditor({
128145
],
129146
image: "",
130147
explanation: "",
148+
advanced: advancedMode,
131149
},
132150
]);
133151
setPreviousQuestionId(newId);
@@ -158,7 +176,7 @@ export function QuizEditor({
158176
});
159177
};
160178

161-
const updateQuestion = (updated: Question) => {
179+
const updateQuestion = (updated: QuestionWithAdvanced) => {
162180
setQuestions((previous) =>
163181
previous.map((q) => (q.id === updated.id ? updated : q)),
164182
);
@@ -171,7 +189,7 @@ export function QuizEditor({
171189
const draft = {
172190
title,
173191
description,
174-
questions: sanitizeQuestions(questions, advancedMode),
192+
questions: sanitizeQuestions(questions),
175193
};
176194

177195
draft.title = draft.title.trim();
@@ -223,7 +241,7 @@ export function QuizEditor({
223241
</div>
224242
<div className="bg-muted/40 flex items-center justify-between gap-3 rounded-md border px-3 py-2">
225243
<Label htmlFor="advanced-mode" className="cursor-pointer">
226-
Tryb zaawansowany
244+
Tryb zaawansowany (domyślny dla nowych pytań)
227245
</Label>
228246
<Switch
229247
id="advanced-mode"
@@ -265,27 +283,20 @@ export function QuizEditor({
265283
}}
266284
/>
267285
</div>
268-
{advancedMode ? (
269-
<div className="space-y-2">
270-
<div className="flex items-center gap-3">
271-
<Checkbox
272-
id="all-multiple"
273-
checked={allQuestionsMultiple === true}
274-
className={
275-
allQuestionsMultiple === null
276-
? "bg-yellow-500/20 dark:bg-yellow-500/30"
277-
: ""
278-
}
279-
onCheckedChange={(checked) => {
280-
setAllQuestionsMultiple(Boolean(checked));
281-
}}
282-
/>
283-
<Label htmlFor="all-multiple" className="cursor-pointer">
284-
Wielokrotny wybór (dla wszystkich pytań)
285-
</Label>
286-
</div>
286+
<div className="space-y-2">
287+
<div className="flex items-center gap-3">
288+
<Checkbox
289+
id="all-multiple"
290+
checked={allQuestionsMultiple ?? "indeterminate"}
291+
onCheckedChange={(checked) => {
292+
setAllQuestionsMultiple(Boolean(checked));
293+
}}
294+
/>
295+
<Label htmlFor="all-multiple" className="cursor-pointer">
296+
Wielokrotny wybór (dla wszystkich pytań)
297+
</Label>
287298
</div>
288-
) : null}
299+
</div>
289300
<div className="space-y-3">
290301
<div className="flex items-center justify-between">
291302
<h2 className="text-sm font-semibold">Pytania</h2>
@@ -300,7 +311,6 @@ export function QuizEditor({
300311
question={q}
301312
onUpdate={updateQuestion}
302313
onRemove={removeQuestion}
303-
advancedMode={advancedMode}
304314
/>
305315
))}
306316
</div>

0 commit comments

Comments
 (0)