Boost Azure Demo
This commit is contained in:
@@ -0,0 +1,240 @@
|
||||
import type { Component } from "solid-js";
|
||||
import { createMemo, createResource, createSignal, onMount } from "solid-js";
|
||||
import { createStore } from "solid-js/store";
|
||||
import { useAuth } from "~/context/auth/context";
|
||||
import styles from "./dashboard-teacher-assignments.module.scss";
|
||||
import { createTeacherAssignment, generateTeacherQuestions, getTeacherAssignmentSetupData } from "./dashboard-teacher-assignments.data";
|
||||
import { defaultGenerationForm, emptySetupForm } from "./dashboard-teacher-assignment-create.constants";
|
||||
import { buildGenerationSuccessMessage, buildMixedGenerationInput, mergeSelectedQuestionIds, parseOptionalInteger } from "./dashboard-teacher-assignment-create.helpers";
|
||||
import {
|
||||
AssignmentCreateHero,
|
||||
AssignmentDetailsSection,
|
||||
PersonalizedGenerationSection,
|
||||
QuestionBankSection,
|
||||
SetupSummarySection,
|
||||
SharedGenerationSection,
|
||||
SubmissionSection,
|
||||
} from "./dashboard-teacher-assignment-create.sections";
|
||||
import type { AssignmentSetupForm, QuestionGenerationForm } from "./dashboard-teacher-assignment-create.types";
|
||||
|
||||
const DashboardTeacherAssignmentCreate: Component = () => {
|
||||
const auth = useAuth();
|
||||
const [teacherId, setTeacherId] = createSignal<number | null>(null);
|
||||
const [setupData, { mutate: mutateSetupData }] = createResource(teacherId, getTeacherAssignmentSetupData);
|
||||
const [form, setForm] = createStore<AssignmentSetupForm>(emptySetupForm);
|
||||
const [generationForm, setGenerationForm] = createStore<QuestionGenerationForm>(defaultGenerationForm);
|
||||
const [isSubmitting, setIsSubmitting] = createSignal(false);
|
||||
const [isGenerating, setIsGenerating] = createSignal(false);
|
||||
const [errorMessage, setErrorMessage] = createSignal<string | null>(null);
|
||||
const [successMessage, setSuccessMessage] = createSignal<string | null>(null);
|
||||
|
||||
onMount(() => {
|
||||
if (auth.user()?.role === "teacher") {
|
||||
setTeacherId(auth.user()!.id);
|
||||
}
|
||||
});
|
||||
|
||||
const selectedClassroom = createMemo(() => setupData()?.classrooms.find((classroom) => `${classroom.id}` === form.classroomId));
|
||||
const selectedQuestionCount = createMemo(() => form.selectedQuestionIds.length);
|
||||
const mixedGenerationEnabled = createMemo(() => form.useMixedGeneration);
|
||||
const mixedGenerationQuestionCount = createMemo(() => Number(form.totalQuestions) || 0);
|
||||
const mixedGenerationRatioLabel = createMemo(() => {
|
||||
const parsed = Number(form.personalizedRatio);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) return "30% personalized";
|
||||
return `${parsed}% personalized`;
|
||||
});
|
||||
|
||||
const handleTextInput =
|
||||
(
|
||||
field:
|
||||
| "classroomId"
|
||||
| "title"
|
||||
| "instructions"
|
||||
| "dueAt"
|
||||
| "primaryTopic"
|
||||
| "primaryDifficulty"
|
||||
| "totalQuestions"
|
||||
| "personalizedRatio"
|
||||
| "seed"
|
||||
| "personalizedDifficulty"
|
||||
| "subject",
|
||||
) =>
|
||||
(event: Event) => {
|
||||
const target = event.currentTarget as HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement;
|
||||
setForm(field, target.value);
|
||||
setSuccessMessage(null);
|
||||
};
|
||||
|
||||
const handleMixedGenerationToggle = (event: Event) => {
|
||||
const target = event.currentTarget as HTMLInputElement;
|
||||
setForm("useMixedGeneration", target.checked);
|
||||
setSuccessMessage(null);
|
||||
};
|
||||
|
||||
const handleGenerationInput = (field: keyof QuestionGenerationForm) => (event: Event) => {
|
||||
const target = event.currentTarget as HTMLInputElement | HTMLSelectElement;
|
||||
setGenerationForm(field, target.value as QuestionGenerationForm[keyof QuestionGenerationForm]);
|
||||
setSuccessMessage(null);
|
||||
};
|
||||
|
||||
const handleQuestionToggle = (questionId: number) => (event: Event) => {
|
||||
const target = event.currentTarget as HTMLInputElement;
|
||||
setForm("selectedQuestionIds", target.checked ? [...form.selectedQuestionIds, questionId] : form.selectedQuestionIds.filter((id) => id !== questionId));
|
||||
setSuccessMessage(null);
|
||||
};
|
||||
|
||||
const resetForm = () => {
|
||||
setForm({ ...emptySetupForm, classroomId: form.classroomId });
|
||||
};
|
||||
|
||||
const mergeGeneratedQuestions = (generatedQuestionIds: number[], questions: Awaited<ReturnType<typeof generateTeacherQuestions>>["questions"]) => {
|
||||
mutateSetupData((current) => {
|
||||
if (!current) return current;
|
||||
const existingQuestions = current.questions.filter((question) => !generatedQuestionIds.includes(question.id));
|
||||
return {
|
||||
...current,
|
||||
questions: [...questions, ...existingQuestions],
|
||||
};
|
||||
});
|
||||
|
||||
setForm("selectedQuestionIds", mergeSelectedQuestionIds(form.selectedQuestionIds, generatedQuestionIds));
|
||||
};
|
||||
|
||||
const handleGenerateQuestions = async (event: Event) => {
|
||||
event.preventDefault();
|
||||
const currentTeacherId = teacherId();
|
||||
if (!currentTeacherId) {
|
||||
setErrorMessage("Your teacher session is still loading.");
|
||||
return;
|
||||
}
|
||||
|
||||
const parsedCount = Number(generationForm.count);
|
||||
const parsedSeed = parseOptionalInteger(generationForm.seed);
|
||||
|
||||
if (!Number.isInteger(parsedCount) || parsedCount < 1 || parsedCount > 25) {
|
||||
setErrorMessage("Choose a question count between 1 and 25.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (generationForm.seed.trim() && (!Number.isInteger(parsedSeed) || Number.isNaN(parsedSeed))) {
|
||||
setErrorMessage("Seed must be a whole number when provided.");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsGenerating(true);
|
||||
setErrorMessage(null);
|
||||
setSuccessMessage(null);
|
||||
|
||||
try {
|
||||
const result = await generateTeacherQuestions({
|
||||
topic: generationForm.topic,
|
||||
difficulty: generationForm.difficulty,
|
||||
count: parsedCount,
|
||||
seed: parsedSeed,
|
||||
});
|
||||
|
||||
mergeGeneratedQuestions(result.generatedQuestionIds, result.questions);
|
||||
setSuccessMessage(buildGenerationSuccessMessage(generationForm, result.count, result.seed));
|
||||
} catch (error) {
|
||||
setErrorMessage(error instanceof Error ? error.message : "Unable to generate questions right now.");
|
||||
} finally {
|
||||
setIsGenerating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSubmit = async (event: Event) => {
|
||||
event.preventDefault();
|
||||
const currentTeacherId = teacherId();
|
||||
if (!currentTeacherId) {
|
||||
setErrorMessage("Your teacher session is still loading.");
|
||||
return;
|
||||
}
|
||||
|
||||
setIsSubmitting(true);
|
||||
setErrorMessage(null);
|
||||
setSuccessMessage(null);
|
||||
|
||||
try {
|
||||
const mixedGeneration = buildMixedGenerationInput(form);
|
||||
|
||||
await createTeacherAssignment({
|
||||
teacherId: currentTeacherId,
|
||||
classroomId: Number(form.classroomId),
|
||||
title: form.title,
|
||||
instructions: form.instructions,
|
||||
dueAt: form.dueAt,
|
||||
selectedQuestionIds: form.selectedQuestionIds,
|
||||
mixedGeneration,
|
||||
});
|
||||
setSuccessMessage(
|
||||
mixedGenerationEnabled()
|
||||
? "Personalized homework created and assigned. Each student now has a mixed generated question set."
|
||||
: "Homework created and assigned to the selected class.",
|
||||
);
|
||||
resetForm();
|
||||
} catch (error) {
|
||||
setErrorMessage(error instanceof Error ? error.message : "Unable to create homework right now.");
|
||||
} finally {
|
||||
setIsSubmitting(false);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<section class={styles.section}>
|
||||
<AssignmentCreateHero />
|
||||
|
||||
<form class={styles.setupGrid} onSubmit={(event) => void handleSubmit(event)}>
|
||||
<article class={styles.setupCard}>
|
||||
<AssignmentDetailsSection
|
||||
form={form}
|
||||
classrooms={setupData()?.classrooms ?? []}
|
||||
selectedQuestionCount={selectedQuestionCount()}
|
||||
onInput={handleTextInput}
|
||||
/>
|
||||
|
||||
<PersonalizedGenerationSection
|
||||
form={form}
|
||||
mixedGenerationEnabled={mixedGenerationEnabled()}
|
||||
mixedGenerationQuestionCount={mixedGenerationQuestionCount()}
|
||||
mixedGenerationRatioLabel={mixedGenerationRatioLabel()}
|
||||
onInput={handleTextInput}
|
||||
onToggle={handleMixedGenerationToggle}
|
||||
/>
|
||||
|
||||
<SharedGenerationSection
|
||||
form={generationForm}
|
||||
isGenerating={isGenerating()}
|
||||
isSubmitting={isSubmitting()}
|
||||
onInput={handleGenerationInput}
|
||||
onGenerate={(event) => void handleGenerateQuestions(event)}
|
||||
/>
|
||||
|
||||
<QuestionBankSection
|
||||
questions={setupData()?.questions ?? []}
|
||||
loading={setupData.loading}
|
||||
mixedGenerationEnabled={mixedGenerationEnabled()}
|
||||
selectedQuestionIds={form.selectedQuestionIds}
|
||||
onToggle={handleQuestionToggle}
|
||||
/>
|
||||
|
||||
<SubmissionSection
|
||||
isSubmitting={isSubmitting()}
|
||||
errorMessage={errorMessage()}
|
||||
successMessage={successMessage()}
|
||||
onReset={resetForm}
|
||||
/>
|
||||
</article>
|
||||
|
||||
<SetupSummarySection
|
||||
classroomName={selectedClassroom()?.name ?? ""}
|
||||
rosterLabel={selectedClassroom()?.studentCountLabel ?? ""}
|
||||
mixedGenerationEnabled={mixedGenerationEnabled()}
|
||||
mixedGenerationQuestionCount={mixedGenerationQuestionCount()}
|
||||
selectedQuestionCount={selectedQuestionCount()}
|
||||
/>
|
||||
</form>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default DashboardTeacherAssignmentCreate;
|
||||
Reference in New Issue
Block a user