Skip to content

Add new quiz set attributes #3047

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 48 commits into from
Jun 9, 2025
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
795bb8a
feat(Quizzes): Add create quizz set when finish creating quizzes - Ch…
Puppychan May 29, 2025
054cc4a
fix(Quiz Sets): Quizzes not displayed in Quiz Sets after finishing cr…
Puppychan May 29, 2025
727be5f
fix(Quiz Sets): Quizzes not displayed in Quiz Sets after finishing cr…
Puppychan May 29, 2025
ae8b50e
feat(quizset): add statistics page for each quiz set
Henry7482 May 30, 2025
96b89a0
feat (UI - Quizzes); display Quizzes following Quiz-sets, add post me…
Puppychan Jun 4, 2025
fc1939d
fix: move mutton for statistics
Henry7482 Jun 4, 2025
86c1590
feat(Taking Quiz): add taking page including backend routes, add temp…
Puppychan Jun 5, 2025
08eeb57
refractor(Taking Quiz): Clean files
Puppychan Jun 5, 2025
a062cbd
chore: remove unused components
Henry7482 Jun 6, 2025
80005e3
fix: temporarily bypass type error by casting workspace_quiz_attempts…
Henry7482 Jun 6, 2025
65ba00f
style: apply prettier formatting
Henry7482 Jun 6, 2025
7780c76
style: apply prettier formatting for feat/upskii/quiz-set-statistics …
vhpx Jun 6, 2025
f9ed408
feat (Taking Quiz); chang ebackend and frontend for more sets attributes
Puppychan Jun 8, 2025
5275499
fix(taking quizz): fix deploy
Puppychan Jun 8, 2025
2de148e
fix<(taking quizz): fix deploy
Puppychan Jun 8, 2025
2db25ca
devs(TakingQuiz); Fix deploy
Puppychan Jun 8, 2025
b3ae5e5
devs(TakingQuiz); Fix deploy
Puppychan Jun 8, 2025
06e1d54
style: apply prettier formatting
Puppychan Jun 8, 2025
f66ca3f
style: apply prettier formatting for feat/upskii/taking-quiz (#3058)
Puppychan Jun 8, 2025
c0fe66c
style: apply prettier formatting
Puppychan Jun 8, 2025
a412b36
style: apply prettier formatting for feat/upskii/taking-quiz (#3059)
vhpx Jun 9, 2025
6315b24
style: apply prettier formatting
vhpx Jun 9, 2025
aca2927
style: apply prettier formatting for feat/upskii/taking-quiz (#3060)
vhpx Jun 9, 2025
e655ec2
Merge branch 'feat/upskii/taking-quiz' into feat/upskii/quiz-set-stat…
Henry7482 Jun 9, 2025
84a5711
style: apply prettier formatting
Henry7482 Jun 9, 2025
ffeb594
Merge branch 'main' into feat/upskii/taking-quiz
vhpx Jun 9, 2025
f845860
chore(db): consolidate migration files
vhpx Jun 9, 2025
447ce10
fix (Taking Quiz): use client misused bug
Puppychan Jun 9, 2025
44831ea
style: apply prettier formatting
vhpx Jun 9, 2025
43edac1
style: apply prettier formatting for feat/upskii/taking-quiz (#3061)
vhpx Jun 9, 2025
678aeec
style: apply prettier formatting for feat/upskii/quiz-set-statistics …
vhpx Jun 9, 2025
ea2a4b4
style: apply prettier formatting
vhpx Jun 9, 2025
8d6238d
style: apply prettier formatting for feat/upskii/taking-quiz (#3065)
Puppychan Jun 9, 2025
a43abfd
refactor: update TakeQuiz component to use hooks and improve error ha…
vhpx Jun 9, 2025
dfedddd
Merge remote-tracking branch 'origin/feat/upskii/taking-quiz' into fe…
vhpx Jun 9, 2025
7bbd5c3
Merge branch 'feat/upskii/taking-quiz' into feat/upskii/quiz-set-stat…
vhpx Jun 9, 2025
01e4449
refactor: remove outdated file references in quiz set routes
vhpx Jun 9, 2025
c92c4dd
Merge branch 'feat/upskii/taking-quiz' into feat/upskii/quiz-set-stat…
vhpx Jun 9, 2025
29f86c6
refractor(Taking Quiz): Remove bug formatted comment
Puppychan Jun 9, 2025
b48d00b
style: apply prettier formatting
vhpx Jun 9, 2025
c05a751
Merge remote-tracking branch 'origin/fix/prettier-formatting-feat/ups…
Puppychan Jun 9, 2025
4351720
Merge branch 'main' into feat/upskii/taking-quiz
Puppychan Jun 9, 2025
30e8ddc
fix (Taking Quiz): fix deploy
Puppychan Jun 9, 2025
61733be
Merge remote-tracking branch 'origin/feat/upskii/taking-quiz' into fe…
Puppychan Jun 9, 2025
a977b5b
style: apply prettier formatting
Puppychan Jun 9, 2025
ae4a65d
style: apply prettier formatting for feat/upskii/taking-quiz (#3069)
Puppychan Jun 9, 2025
dd4a0b0
fix (Taking Quiz): error 404 in all pages
Puppychan Jun 9, 2025
0a15b8a
Merge remote-tracking branch 'origin/feat/upskii/taking-quiz' into fe…
Puppychan Jun 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions apps/db/supabase/migrations/20250604125645_new_migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
create table "public"."workspace_quiz_attempt_answers" (
"id" uuid not null default gen_random_uuid(),
"attempt_id" uuid not null,
"quiz_id" uuid not null,
"selected_option_id" uuid not null,
"is_correct" boolean not null,
"score_awarded" real not null
);


create table "public"."workspace_quiz_attempts" (
"id" uuid not null default gen_random_uuid(),
"user_id" uuid not null,
"set_id" uuid not null,
"attempt_number" integer not null,
"started_at" timestamp with time zone not null default now(),
"completed_at" timestamp with time zone,
"total_score" real
);


alter table "public"."workspace_quiz_sets" add column "attempt_limit" integer;

alter table "public"."workspace_quiz_sets" add column "time_limit_minutes" integer;

alter table "public"."workspace_quizzes" add column "score" integer not null default 1;

CREATE UNIQUE INDEX workspace_quiz_attempts_pkey ON public.workspace_quiz_attempts USING btree (id);

CREATE UNIQUE INDEX wq_answer_pkey ON public.workspace_quiz_attempt_answers USING btree (id);

CREATE UNIQUE INDEX wq_attempts_unique ON public.workspace_quiz_attempts USING btree (user_id, set_id, attempt_number);

alter table "public"."workspace_quiz_attempt_answers" add constraint "wq_answer_pkey" PRIMARY KEY using index "wq_answer_pkey";

alter table "public"."workspace_quiz_attempts" add constraint "workspace_quiz_attempts_pkey" PRIMARY KEY using index "workspace_quiz_attempts_pkey";

alter table "public"."workspace_quiz_attempt_answers" add constraint "wq_answer_attempt_fkey" FOREIGN KEY (attempt_id) REFERENCES workspace_quiz_attempts(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;

alter table "public"."workspace_quiz_attempt_answers" validate constraint "wq_answer_attempt_fkey";

alter table "public"."workspace_quiz_attempt_answers" add constraint "wq_answer_option_fkey" FOREIGN KEY (selected_option_id) REFERENCES quiz_options(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;

alter table "public"."workspace_quiz_attempt_answers" validate constraint "wq_answer_option_fkey";

alter table "public"."workspace_quiz_attempt_answers" add constraint "wq_answer_quiz_fkey" FOREIGN KEY (quiz_id) REFERENCES workspace_quizzes(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;

alter table "public"."workspace_quiz_attempt_answers" validate constraint "wq_answer_quiz_fkey";

alter table "public"."workspace_quiz_attempts" add constraint "wq_attempts_set_fkey" FOREIGN KEY (set_id) REFERENCES workspace_quiz_sets(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;

alter table "public"."workspace_quiz_attempts" validate constraint "wq_attempts_set_fkey";

alter table "public"."workspace_quiz_attempts" add constraint "wq_attempts_unique" UNIQUE using index "wq_attempts_unique";

alter table "public"."workspace_quiz_attempts" add constraint "wq_attempts_user_fkey" FOREIGN KEY (user_id) REFERENCES users(id) ON UPDATE CASCADE ON DELETE CASCADE not valid;

alter table "public"."workspace_quiz_attempts" validate constraint "wq_attempts_user_fkey";

grant delete on table "public"."workspace_quiz_attempt_answers" to "anon";

grant insert on table "public"."workspace_quiz_attempt_answers" to "anon";

grant references on table "public"."workspace_quiz_attempt_answers" to "anon";

grant select on table "public"."workspace_quiz_attempt_answers" to "anon";

grant trigger on table "public"."workspace_quiz_attempt_answers" to "anon";

grant truncate on table "public"."workspace_quiz_attempt_answers" to "anon";

grant update on table "public"."workspace_quiz_attempt_answers" to "anon";

grant delete on table "public"."workspace_quiz_attempt_answers" to "authenticated";

grant insert on table "public"."workspace_quiz_attempt_answers" to "authenticated";

grant references on table "public"."workspace_quiz_attempt_answers" to "authenticated";

grant select on table "public"."workspace_quiz_attempt_answers" to "authenticated";

grant trigger on table "public"."workspace_quiz_attempt_answers" to "authenticated";

grant truncate on table "public"."workspace_quiz_attempt_answers" to "authenticated";

grant update on table "public"."workspace_quiz_attempt_answers" to "authenticated";

grant delete on table "public"."workspace_quiz_attempt_answers" to "service_role";

grant insert on table "public"."workspace_quiz_attempt_answers" to "service_role";

grant references on table "public"."workspace_quiz_attempt_answers" to "service_role";

grant select on table "public"."workspace_quiz_attempt_answers" to "service_role";

grant trigger on table "public"."workspace_quiz_attempt_answers" to "service_role";

grant truncate on table "public"."workspace_quiz_attempt_answers" to "service_role";

grant update on table "public"."workspace_quiz_attempt_answers" to "service_role";

grant delete on table "public"."workspace_quiz_attempts" to "anon";

grant insert on table "public"."workspace_quiz_attempts" to "anon";

grant references on table "public"."workspace_quiz_attempts" to "anon";

grant select on table "public"."workspace_quiz_attempts" to "anon";

grant trigger on table "public"."workspace_quiz_attempts" to "anon";

grant truncate on table "public"."workspace_quiz_attempts" to "anon";

grant update on table "public"."workspace_quiz_attempts" to "anon";

grant delete on table "public"."workspace_quiz_attempts" to "authenticated";

grant insert on table "public"."workspace_quiz_attempts" to "authenticated";

grant references on table "public"."workspace_quiz_attempts" to "authenticated";

grant select on table "public"."workspace_quiz_attempts" to "authenticated";

grant trigger on table "public"."workspace_quiz_attempts" to "authenticated";

grant truncate on table "public"."workspace_quiz_attempts" to "authenticated";

grant update on table "public"."workspace_quiz_attempts" to "authenticated";

grant delete on table "public"."workspace_quiz_attempts" to "service_role";

grant insert on table "public"."workspace_quiz_attempts" to "service_role";

grant references on table "public"."workspace_quiz_attempts" to "service_role";

grant select on table "public"."workspace_quiz_attempts" to "service_role";

grant trigger on table "public"."workspace_quiz_attempts" to "service_role";

grant truncate on table "public"."workspace_quiz_attempts" to "service_role";

grant update on table "public"."workspace_quiz_attempts" to "service_role";


20 changes: 20 additions & 0 deletions apps/db/supabase/migrations/20250605064241_new_migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
alter table "public"."workspace_quiz_sets" add column "allow_view_results" boolean not null default true;

alter table "public"."workspace_quiz_sets" add column "release_at" timestamp with time zone;

alter table "public"."workspace_quiz_sets" add column "release_points_immediately" boolean not null default true;

set check_function_bodies = off;

CREATE OR REPLACE FUNCTION public.sum_quiz_scores(p_set_id uuid)
RETURNS TABLE(sum numeric)
LANGUAGE sql
AS $function$
SELECT COALESCE(SUM(wq.score), 0)::numeric
FROM quiz_set_quizzes qsq
JOIN workspace_quizzes wq ON qsq.quiz_id = wq.id
WHERE qsq.set_id = p_set_id;
$function$
;


5 changes: 5 additions & 0 deletions apps/db/supabase/migrations/20250606073849_new_migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
alter table "public"."workspace_quiz_sets" add column "due_date" timestamp with time zone not null default (now() + '7 days'::interval);

alter table "public"."workspace_quiz_sets" add column "results_released" boolean not null default false;


5 changes: 5 additions & 0 deletions apps/db/supabase/migrations/20250608051026_new_migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
alter table "public"."workspace_quiz_sets" drop column "release_at";

alter table "public"."workspace_quiz_sets" drop column "results_released";


33 changes: 32 additions & 1 deletion apps/upskii/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3780,9 +3780,40 @@
"edit": "Edit quiz",
"question": "Question",
"answer": "Answer",
"question_status_title": "Question Progress",
"answered_status_short": "answered",
"quiz_progress_label": "Quiz Progress",
"question_navigation_label": "Question Navigation",
"jump_to_question_aria": "Question {{number}}, {{status}}",
"answered_state": "Answered",
"unanswered_state": "Unanswered",
"answered_icon": "✓",
"unanswered_icon": "⚪",
"time_elapsed": "Time Elapsed",
"hidden_time_elapsed": "Time Elapsed (hidden)",
"hidden_time_remaining": "Hidden Timer",
"edit_description": "Edit an existing quiz",
"generation_error": "An error has occured when generating quizzes.",
"generation_accepted": "AI-generated quizzes are accepted!"
"generation_accepted": "AI-generated quizzes are accepted!",
"please_answer_all": "Please answer all questions.",
"loading": "Loading...",
"results": "Results",
"attempt": "Attempt",
"of": "of",
"unlimited": "Unlimited Attempts",
"score": "Score",
"done": "Done",
"attempts": "Attempts",
"time_limit": "Time Limit",
"no_time_limit": "No Time Limit",
"minutes": "Minutes",
"take_quiz": "Take Quiz",
"time_remaining": "Time Remaining",
"points": "Points",
"submitting": "Submitting...",
"submit": "Submit",
"due_on": "Due on",
"quiz_past_due": "This quiz is past its due date."
},
"ws-reports": {
"report": "Report",
Expand Down
33 changes: 32 additions & 1 deletion apps/upskii/messages/vi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3780,9 +3780,40 @@
"edit": "Chỉnh sửa bộ trắc nghiệm",
"question": "Câu hỏi",
"answer": "Câu trả lời",
"question_status_title": "Tiến độ câu hỏi",
"answered_status_short": "đã trả lời",
"quiz_progress_label": "Tiến độ bài kiểm tra",
"question_navigation_label": "Điều hướng câu hỏi",
"jump_to_question_aria": "Câu hỏi {{number}}, {{status}}",
"answered_state": "Đã trả lời",
"unanswered_state": "Chưa trả lời",
"answered_icon": "✓",
"unanswered_icon": "⚪",
"time_elapsed": "Đã trôi qua",
"hidden_time_elapsed": "Đã ẩn thời gian",
"hidden_time_remaining": "Ẩn đếm ngược",
"edit_description": "Chỉnh sửa bài kiểm tra hiện có",
"generation_error": "Đã xảy ra lỗi khi tạo bộ câu hỏi.",
"generation_accepted": "Các bộ câu hỏi do AI tạo ra đã được chấp nhận!"
"generation_accepted": "Các bộ câu hỏi do AI tạo ra đã được chấp nhận!",
"please_answer_all": "Vui lòng trả lời tất cả các câu hỏi.",
"loading": "Đang tải...",
"results": "Kết quả",
"attempt": "Lần thử",
"of": "của",
"unlimited": "Không giới hạn số lần thi",
"score": "Điểm số",
"done": "Hoàn thành",
"attempts": "Số lần thử",
"time_limit": "Giới hạn thời gian",
"no_time_limit": "Không giới hạn thời gian",
"minutes": "Phút",
"take_quiz": "Làm bài kiểm tra",
"time_remaining": "Thời gian còn lại",
"points": "Điểm",
"submitting": "Đang gửi...",
"submit": "Nộp bài",
"due_on": "Hạn nộp",
"quiz_past_due": "Bài kiểm tra đã quá hạn"
},
"ws-reports": {
"report": "Báo cáo",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default async function UserGroupDetailsPage({ params }: Props) {
const storagePath = `${wsId}/courses/${courseId}/modules/${moduleId}/resources/`;
const resources = await getResources({ path: storagePath });
const flashcards = await getFlashcards(moduleId);
const quizzes = await getQuizzes(moduleId);
const quizSets = await getQuizzes(moduleId);

const cards = flashcards.map((fc) => ({
id: fc.id,
Expand Down Expand Up @@ -134,12 +134,12 @@ export default async function UserGroupDetailsPage({ params }: Props) {
title={t('ws-quizzes.plural')}
icon={<ListTodo className="h-5 w-5" />}
content={
quizzes && quizzes.length > 0 ? (
quizSets && quizSets.length > 0 ? (
<div className="grid gap-4 pt-2 md:grid-cols-2">
<ClientQuizzes
wsId={wsId}
moduleId={moduleId}
quizzes={quizzes}
quizSets={quizSets}
previewMode
/>
</div>
Expand Down Expand Up @@ -229,20 +229,77 @@ const getFlashcards = async (moduleId: string) => {
};

const getQuizzes = async (moduleId: string) => {
// created_at: "2025-05-29T08:12:16.653395+00:00"
// id: "426d031f-2dc4-4370-972d-756af04288fb"
// question: "What are the main building blocks of a NestJS application?"
// quiz_options: (4) [{…}, {…}, {…}, {…}]
// ws_id: "00000000-0000-0000-0000-000000000000"
const supabase = await createClient();

const { data, error } = await supabase
.from('course_module_quizzes')
.select('...workspace_quizzes(*, quiz_options(*))')
.select(
`
quiz_id,
workspace_quizzes (
id,
question,
created_at,
ws_id,
quiz_options(*),
quiz_set_quizzes(
set_id,
workspace_quiz_sets(name)
)
)
`
)
.eq('module_id', moduleId);

if (error) {
console.error('error', error);
console.error('Error fetching grouped quizzes:', error);
return [];
}

return data || [];
const grouped = new Map<
string,
{
setId: string;
setName: string;
quizzes: any[];
}
>();

for (const cmq of data || []) {
const quiz = cmq.workspace_quizzes;
const setData = quiz?.quiz_set_quizzes?.[0]; // assume only one set

if (!quiz || !setData) continue;

const setId = setData.set_id;
const setName = setData.workspace_quiz_sets?.name || 'Unnamed Set';

if (!grouped.has(setId)) {
grouped.set(setId, {
setId,
setName,
quizzes: [],
});
}

grouped.get(setId)!.quizzes.push({
id: quiz.id,
question: quiz.question,
quiz_options: quiz.quiz_options,
created_at: quiz.created_at,
ws_id: quiz.ws_id,
});
}

return Array.from(grouped.values());
};


async function getResources({ path }: { path: string }) {
const supabase = await createDynamicClient();

Expand Down
Loading
Loading