+
Page {currentPage} of {totalPages}
diff --git a/src/lib/admin/SortDropdown.svelte b/src/lib/admin/SortDropdown.svelte
deleted file mode 100644
index e24c6d2..0000000
--- a/src/lib/admin/SortDropdown.svelte
+++ /dev/null
@@ -1,71 +0,0 @@
-
-
-
-
-
-
- {#if open}
-
- {#each columns as col (col.key)}
-
- {/each}
-
- {/if}
-
diff --git a/src/lib/admin/types.ts b/src/lib/admin/types.ts
index add7fe2..0a94875 100644
--- a/src/lib/admin/types.ts
+++ b/src/lib/admin/types.ts
@@ -61,3 +61,11 @@ export interface GradeInput {
max_score: number;
remarks?: string;
}
+
+/** Row shape for the constiquiz respondent list page (P02-001). */
+export interface QuizRespondent {
+ user_id: string;
+ full_name: string;
+ status: 'Not Started' | 'In Progress' | 'Completed';
+ current_score: number;
+}
diff --git a/src/lib/server/admin-queries.ts b/src/lib/server/admin-queries.ts
index dbf8875..cf4f023 100644
--- a/src/lib/server/admin-queries.ts
+++ b/src/lib/server/admin-queries.ts
@@ -1,6 +1,7 @@
import type {
ApplicantProfile,
QuizAnswerDetail,
+ QuizRespondent,
QuizResultDetail,
QuizResultSummary,
SigsheetProgressSummary,
@@ -134,6 +135,71 @@ export async function fetchSigsheetProgress(
};
}
+export async function fetchQuizRespondents(
+ supabase: SupabaseClient,
+): Promise<{ respondents: QuizRespondent[]; max_score: number }> {
+ const [applicantsRes, submissionsRes, answersRes, questionsRes] = await Promise.all([
+ supabase.from('profiles').select('id, full_name').eq('role', 'applicant'),
+ supabase.from('constiquiz-submissions').select('user_id'),
+ supabase.from('constiquiz-answers').select('user_id, points'),
+ supabase.from('constiquiz-questions').select('point_value'),
+ ]);
+
+ if (applicantsRes.error) {
+ throw new Error(applicantsRes.error.message);
+ }
+ if (submissionsRes.error) {
+ throw new Error(submissionsRes.error.message);
+ }
+ if (answersRes.error) {
+ throw new Error(answersRes.error.message);
+ }
+ if (questionsRes.error) {
+ throw new Error(questionsRes.error.message);
+ }
+
+ const submittedUserIds = new Set();
+ for (const row of (submissionsRes.data as Record[] | null) ?? []) {
+ const uid = row.user_id as string | null;
+ if (uid) {
+ submittedUserIds.add(uid);
+ }
+ }
+
+ const scoreByUser: Record = {};
+ const hasAnswerByUser = new Set();
+ for (const row of (answersRes.data as Record[] | null) ?? []) {
+ const uid = row.user_id as string | null;
+ if (!uid) continue;
+ scoreByUser[uid] = (scoreByUser[uid] ?? 0) + ((row.points as number | null) ?? 0);
+ hasAnswerByUser.add(uid);
+ }
+
+ const max_score = ((questionsRes.data as Record[] | null) ?? []).reduce(
+ (sum, q) => sum + ((q.point_value as number | null) ?? 0),
+ 0,
+ );
+
+ const respondents: QuizRespondent[] = ((applicantsRes.data as Record[] | null) ?? []).map(p => {
+ const uid = p.id as string;
+ let status: QuizRespondent['status'] = 'Not Started';
+ if (submittedUserIds.has(uid)) {
+ status = 'Completed';
+ } else if (hasAnswerByUser.has(uid)) {
+ status = 'In Progress';
+ }
+
+ return {
+ user_id: uid,
+ full_name: (p.full_name as string | null) ?? '',
+ status,
+ current_score: scoreByUser[uid] ?? 0,
+ };
+ });
+
+ return { respondents, max_score };
+}
+
export async function fetchSigsheetDetail(
supabase: SupabaseClient,
userId: string,
diff --git a/src/routes/admin/constiquiz/+page.server.ts b/src/routes/admin/constiquiz/+page.server.ts
new file mode 100644
index 0000000..51f8053
--- /dev/null
+++ b/src/routes/admin/constiquiz/+page.server.ts
@@ -0,0 +1,8 @@
+import type { PageServerLoad } from './$types';
+import { fetchQuizRespondents } from '$lib/server/admin-queries';
+import { getSupabaseAdmin } from '$lib/server/supabaseAdmin';
+
+export const load: PageServerLoad = async () => {
+ const supabase = getSupabaseAdmin();
+ return await fetchQuizRespondents(supabase);
+};
diff --git a/src/routes/admin/constiquiz/+page.svelte b/src/routes/admin/constiquiz/+page.svelte
new file mode 100644
index 0000000..388408f
--- /dev/null
+++ b/src/routes/admin/constiquiz/+page.svelte
@@ -0,0 +1,47 @@
+
+
+[]}
+ {columns}
+ rowKey="user_id"
+ filterKey="status"
+ onRowClick={goToDetail}
+>
+ {#snippet cell({ row, column })}
+ {#if column.key === 'status'}
+
+ {:else if column.key === 'current_score'}
+
+ {row.current_score} / {data.max_score}
+
+ {:else if column.key === 'responses'}
+ e.stopPropagation()}
+ >
+ View response
+
+ {:else}
+ {String(row[column.key] ?? '')}
+ {/if}
+ {/snippet}
+