From c82060ec67a70739f3ed61b973923eae3b3c46b9 Mon Sep 17 00:00:00 2001 From: kjassani Date: Mon, 23 Feb 2026 12:12:13 -0800 Subject: [PATCH 01/13] new API response shape to send visible quesion and count hidden --- packages/common/index.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/common/index.ts b/packages/common/index.ts index c73b50046..6c831f528 100644 --- a/packages/common/index.ts +++ b/packages/common/index.ts @@ -1329,6 +1329,11 @@ export type AsyncQuestion = { votesSum: number } +export type GetAsyncQuestionsResponse = { + questions: AsyncQuestion[] + hiddenPrivateQuestionsCount: number +} + /** * An async question is created when a student wants help from a TA. */ From 573cd51f6d187d6d65d3329b9f7c98aeb0299ddf Mon Sep 17 00:00:00 2001 From: kjassani Date: Mon, 23 Feb 2026 12:14:32 -0800 Subject: [PATCH 02/13] updating GET /asyncQuestions/:courseId to return new API response shape --- .../asyncQuestion/asyncQuestion.controller.ts | 54 ++++++++++++------- 1 file changed, 35 insertions(+), 19 deletions(-) diff --git a/packages/server/src/asyncQuestion/asyncQuestion.controller.ts b/packages/server/src/asyncQuestion/asyncQuestion.controller.ts index 203eab57f..f52e6ee8a 100644 --- a/packages/server/src/asyncQuestion/asyncQuestion.controller.ts +++ b/packages/server/src/asyncQuestion/asyncQuestion.controller.ts @@ -6,6 +6,7 @@ import { asyncQuestionStatus, CreateAsyncQuestions, ERROR_MESSAGES, + GetAsyncQuestionsResponse, nameToRGB, Role, UnreadAsyncQuestionResponse, @@ -743,7 +744,7 @@ export class asyncQuestionController { async getAsyncQuestions( @Param('courseId', ParseIntPipe) courseId: number, @UserId() userId: number, - ): Promise { + ): Promise { const userCourse = await UserCourseModel.findOne({ where: { userId, @@ -794,6 +795,7 @@ export class asyncQuestionController { } let questions: Partial[]; + let hiddenPrivateQuestionsCount = 0; const isStaff: boolean = userCourse.role === Role.TA || userCourse.role === Role.PROFESSOR; @@ -808,23 +810,34 @@ export class asyncQuestionController { ); } else { // Students see their own questions and questions that are visible - questions = ( - await Promise.all( - all.map(async (question) => { - if ( - question.creatorId === userId || - (await this.asyncQuestionService.isVisible( - question, - courseSettings, - )) - ) { - return question; - } else { - return undefined; - } - }), - ) - ).filter((s) => s != undefined); + const visibilityResults = await Promise.all( + all.map(async (question) => { + if (question.creatorId === userId) { + return { question, isHiddenPrivate: false }; + } + + const isVisible = await this.asyncQuestionService.isVisible( + question, + courseSettings, + ); + + if (isVisible) { + return { question, isHiddenPrivate: false }; + } + + return { + question: undefined, + isHiddenPrivate: question.status !== asyncQuestionStatus.TADeleted, + }; + }), + ); + + questions = visibilityResults + .map((result) => result.question) + .filter((question): question is AsyncQuestionModel => !!question); + hiddenPrivateQuestionsCount = visibilityResults.filter( + (result) => result.isHiddenPrivate, + ).length; } questions = questions.map((question: AsyncQuestionModel) => { @@ -930,7 +943,10 @@ export class asyncQuestionController { return temp; }); - return questions as unknown as AsyncQuestion[]; + return { + questions: questions as unknown as AsyncQuestion[], + hiddenPrivateQuestionsCount, + }; } // Moved from userInfo context endpoint as this updates too frequently to make sense caching it with userInfo data From 0c0bdfc68820ed85d2209de598a6ef7d9c0405f9 Mon Sep 17 00:00:00 2001 From: kjassani Date: Mon, 23 Feb 2026 12:20:46 -0800 Subject: [PATCH 03/13] Update hook to use new async questions response --- packages/frontend/app/hooks/useAsyncQuestions.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/frontend/app/hooks/useAsyncQuestions.ts b/packages/frontend/app/hooks/useAsyncQuestions.ts index 8065eabc4..46ff72f62 100644 --- a/packages/frontend/app/hooks/useAsyncQuestions.ts +++ b/packages/frontend/app/hooks/useAsyncQuestions.ts @@ -1,15 +1,17 @@ -import { AsyncQuestion } from '@koh/common' +import { GetAsyncQuestionsResponse } from '@koh/common' import useSWR from 'swr' import { API } from '../api' export function useAsyncQuestions( cid: number, ): [ - AsyncQuestion[] | undefined, + GetAsyncQuestionsResponse | undefined, ( - data?: AsyncQuestion[] | Promise, + data?: + | GetAsyncQuestionsResponse + | Promise, shouldRevalidate?: boolean, - ) => Promise, + ) => Promise, ] { const key = `/api/v1/courses/${cid}/asyncQuestions` From 53d4132462241b4f49199a1a8ad7c2accb8888a7 Mon Sep 17 00:00:00 2001 From: kjassani Date: Mon, 23 Feb 2026 12:21:10 -0800 Subject: [PATCH 04/13] Show hidden private question count and empty states --- .../course/[cid]/async_centre/page.tsx | 89 +++++++++++++++---- 1 file changed, 71 insertions(+), 18 deletions(-) diff --git a/packages/frontend/app/(dashboard)/course/[cid]/async_centre/page.tsx b/packages/frontend/app/(dashboard)/course/[cid]/async_centre/page.tsx index 35cf1e228..c30fd20d9 100644 --- a/packages/frontend/app/(dashboard)/course/[cid]/async_centre/page.tsx +++ b/packages/frontend/app/(dashboard)/course/[cid]/async_centre/page.tsx @@ -12,6 +12,7 @@ import React, { import { Button, Checkbox, + Empty, Pagination, Popover, Segmented, @@ -65,7 +66,11 @@ export default function AsyncCentrePage( const [page, setPage] = useState(1) const [pageSize, setPageSize] = useState(20) - const [asyncQuestions, mutateAsyncQuestions] = useAsyncQuestions(courseId) + const [asyncQuestionsResponse, mutateAsyncQuestions] = + useAsyncQuestions(courseId) + const asyncQuestions = asyncQuestionsResponse?.questions + const hiddenPrivateQuestionsCount = + asyncQuestionsResponse?.hiddenPrivateQuestionsCount ?? 0 const [createAsyncQuestionModalOpen, setCreateAsyncQuestionModalOpen] = useState(false) @@ -207,6 +212,8 @@ export default function AsyncCentrePage( const displayedQuestions = useMemo(() => applySort, [applySort]) const totalQuestions = displayedQuestions.length // total length after all filters applied + const totalPages = Math.max(1, Math.ceil(totalQuestions / pageSize)) + const isLastPage = page >= totalPages // reset to page 1 whenever the filtered question count changes. useEffect(() => { @@ -219,6 +226,17 @@ export default function AsyncCentrePage( return displayedQuestions.slice(startIndex, endIndex) }, [page, pageSize, displayedQuestions]) + const showHiddenPrivateQuestionsNotice = + !isStaff && + hiddenPrivateQuestionsCount > 0 && + paginatedQuestions.length > 0 && + isLastPage + + const showNoQuestionsPostedEmpty = + (asyncQuestions?.length ?? 0) === 0 && hiddenPrivateQuestionsCount === 0 + const showNoPublicOrMineEmpty = + (asyncQuestions?.length ?? 0) === 0 && hiddenPrivateQuestionsCount > 0 + // This endpoint will be called to update unread count back to 0 when this page is entered // May seem more inefficient but this is the only way to ensure that the unread count is accurate given that userInfo no longer tracks it useEffect(() => { @@ -344,7 +362,10 @@ export default function AsyncCentrePage( if (!userInfo) { return - } else if (asyncQuestions === undefined || asyncQuestions === null) { + } else if ( + asyncQuestionsResponse === undefined || + asyncQuestionsResponse === null + ) { return } else { return ( @@ -450,25 +471,57 @@ export default function AsyncCentrePage( showStudents={showStudents} /> ))} + + {showNoQuestionsPostedEmpty && ( +
+ +
+ )} + {showNoPublicOrMineEmpty && ( +
+ +

+ No public or questions you created found +

+

+ +{hiddenPrivateQuestionsCount} additional private + question + {hiddenPrivateQuestionsCount === 1 ? '' : 's'} +

+
+ } + /> + + )} + {showHiddenPrivateQuestionsNotice && ( +

+ +{hiddenPrivateQuestionsCount} additional private question + {hiddenPrivateQuestionsCount === 1 ? '' : 's'} +

+ )} - { - setPage(newPage) - if (newPageSize !== pageSize) { - setPageSize(newPageSize) - setPage(1) // reset to page 1 when page size changes so you don't end up on a page that doesnt exist anymore + {totalQuestions > 0 && ( + { + setPage(newPage) + if (newPageSize !== pageSize) { + setPageSize(newPageSize) + setPage(1) // reset to page 1 when page size changes so you don't end up on a page that doesnt exist anymore + } + }} + showSizeChanger + showTotal={(total, range) => + `${range[0]}-${range[1]} of ${total} questions` } - }} - showSizeChanger - showTotal={(total, range) => - `${range[0]}-${range[1]} of ${total} questions` - } - className="mb-2 mt-4 text-center" - /> + className="mb-2 mt-4 text-center" + /> + )} Date: Mon, 23 Feb 2026 12:22:09 -0800 Subject: [PATCH 05/13] add async questions response type with hidden private count --- packages/frontend/app/api/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/app/api/index.ts b/packages/frontend/app/api/index.ts index 894fd8459..ec659b458 100644 --- a/packages/frontend/app/api/index.ts +++ b/packages/frontend/app/api/index.ts @@ -3,7 +3,6 @@ import { AddChatbotQuestionParams, AddDocumentChunkParams, AllStudentAssignmentProgress, - AsyncQuestion, AsyncQuestionComment, AsyncQuestionCommentParams, AsyncQuestionParams, @@ -40,6 +39,7 @@ import { EditCourseInfoParams, ExtraTAStatus, GetAlertsResponse, + GetAsyncQuestionsResponse, GetAvailableModelsBody, GetChatbotHistoryResponse, GetCourseResponse, @@ -808,7 +808,7 @@ export class APIClient { ), } asyncQuestions = { - get: async (cid: number): Promise => + get: async (cid: number): Promise => this.req('GET', `/api/v1/asyncQuestions/${cid}`, undefined), create: async (body: CreateAsyncQuestions, cid: number) => this.req( From 2d61e1c947d4e43d1cc908105351743f21d71b49 Mon Sep 17 00:00:00 2001 From: kjassani Date: Mon, 23 Feb 2026 12:22:24 -0800 Subject: [PATCH 06/13] Update async question tests for new response format --- .../server/test/asyncQuestion.integration.ts | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/packages/server/test/asyncQuestion.integration.ts b/packages/server/test/asyncQuestion.integration.ts index 49c5ffeb8..d40dfece8 100644 --- a/packages/server/test/asyncQuestion.integration.ts +++ b/packages/server/test/asyncQuestion.integration.ts @@ -1000,7 +1000,8 @@ describe('AsyncQuestion Integration', () => { `/asyncQuestions/${course.id}`, ); expect(response.status).toBe(200); - const questions: AsyncQuestion[] = response.body; + const questions: AsyncQuestion[] = response.body.questions; + expect(response.body.hiddenPrivateQuestionsCount).toBe(1); expect(questions).toHaveLength(2); expect(questions).toMatchSnapshot(); expect(questions).toEqual( @@ -1036,7 +1037,7 @@ describe('AsyncQuestion Integration', () => { `/asyncQuestions/${course.id}`, ); expect(response.status).toBe(200); - const questions: AsyncQuestion[] = response.body; + const questions: AsyncQuestion[] = response.body.questions; expect(questions).toHaveLength(3); expect(questions).toEqual( expect.arrayContaining([ @@ -1065,7 +1066,7 @@ describe('AsyncQuestion Integration', () => { `/asyncQuestions/${course.id}`, ); expect(response.status).toBe(200); - const questions: AsyncQuestion[] = response.body; + const questions: AsyncQuestion[] = response.body.questions; expect(questions).toHaveLength(2); expect(questions).toEqual( expect.arrayContaining([ @@ -1094,7 +1095,7 @@ describe('AsyncQuestion Integration', () => { `/asyncQuestions/${course.id}`, ); expect(response.status).toBe(200); - const questions: AsyncQuestion[] = response.body; + const questions: AsyncQuestion[] = response.body.questions; expect(questions).toHaveLength(2); expect(questions).toEqual( expect.arrayContaining([ @@ -1113,7 +1114,7 @@ describe('AsyncQuestion Integration', () => { `/asyncQuestions/${course.id}`, ); expect(response.status).toBe(200); - const questions: AsyncQuestion[] = response.body; + const questions: AsyncQuestion[] = response.body.questions; expect(questions).toEqual( expect.arrayContaining([ expect.objectContaining({ @@ -1160,7 +1161,8 @@ describe('AsyncQuestion Integration', () => { `/asyncQuestions/${course.id}`, ); expect(response.status).toBe(200); - const questions: AsyncQuestion[] = response.body; + const questions: AsyncQuestion[] = response.body.questions; + expect(response.body.hiddenPrivateQuestionsCount).toBe(0); expect(questions).toHaveLength(3); expect(questions).toEqual( expect.arrayContaining([ @@ -1206,7 +1208,7 @@ describe('AsyncQuestion Integration', () => { `/asyncQuestions/${course.id}`, ); expect(response.status).toBe(200); - const questions: AsyncQuestion[] = response.body; + const questions: AsyncQuestion[] = response.body.questions; expect(questions).toHaveLength(2); expect(questions).toEqual( expect.arrayContaining([ @@ -1261,7 +1263,7 @@ describe('AsyncQuestion Integration', () => { `/asyncQuestions/${course.id}`, ); expect(response2.status).toBe(200); - const questions2: AsyncQuestion[] = response2.body; + const questions2: AsyncQuestion[] = response2.body.questions; expect(questions2).toHaveLength(3); expect(questions2).toEqual( expect.arrayContaining([ @@ -1314,7 +1316,7 @@ describe('AsyncQuestion Integration', () => { `/asyncQuestions/${course.id}`, ); expect(response.status).toBe(200); - const questions: AsyncQuestion[] = response.body; + const questions: AsyncQuestion[] = response.body.questions; expect(questions).toEqual( expect.arrayContaining([ expect.objectContaining({ @@ -1383,8 +1385,8 @@ describe('AsyncQuestion Integration', () => { ); expect(response.status).toBe(200); expect(response2.status).toBe(200); - const questions: AsyncQuestion[] = response.body; - const questions2: AsyncQuestion[] = response2.body; + const questions: AsyncQuestion[] = response.body.questions; + const questions2: AsyncQuestion[] = response2.body.questions; expect(questions).toHaveLength(3); expect(questions).toEqual( expect.arrayContaining([ @@ -1430,9 +1432,9 @@ describe('AsyncQuestion Integration', () => { `/asyncQuestions/${course.id}`, ); expect(response.status).toBe(200); - const questions: AsyncQuestion[] = response.body; - const questions2: AsyncQuestion[] = response2.body; - const questions3: AsyncQuestion[] = response3.body; + const questions: AsyncQuestion[] = response.body.questions; + const questions2: AsyncQuestion[] = response2.body.questions; + const questions3: AsyncQuestion[] = response3.body.questions; const fullInfo = { id: asyncQuestion9.id, isAnonymous: true, @@ -1479,7 +1481,7 @@ describe('AsyncQuestion Integration', () => { `/asyncQuestions/${course.id}`, ); expect(response.status).toBe(200); - const questions: AsyncQuestion[] = response.body; + const questions: AsyncQuestion[] = response.body.questions; expect(questions).toEqual( expect.arrayContaining([ expect.objectContaining({ From 506712a712e6c44c090c42e90d78379160cd0105 Mon Sep 17 00:00:00 2001 From: kjassani Date: Mon, 23 Feb 2026 12:25:20 -0800 Subject: [PATCH 07/13] replace Anytime Question nudge comment --- .../(dashboard)/course/[cid]/components/chatbot/Chatbot.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/frontend/app/(dashboard)/course/[cid]/components/chatbot/Chatbot.tsx b/packages/frontend/app/(dashboard)/course/[cid]/components/chatbot/Chatbot.tsx index f1410277e..35dbfcd5a 100644 --- a/packages/frontend/app/(dashboard)/course/[cid]/components/chatbot/Chatbot.tsx +++ b/packages/frontend/app/(dashboard)/course/[cid]/components/chatbot/Chatbot.tsx @@ -559,14 +559,14 @@ const Chatbot: React.FC = ({ chatbotQuestionType === 'Course' && messages.length > 1 && (
- Unhappy with your answer?{' '} + Discuss or verify this with your professor/TA:{' '} - Convert to anytime question + Convert to Anytime Question
)} From a634247bf2f6814310e9978fd4b496d92984fcd7 Mon Sep 17 00:00:00 2001 From: kjassani Date: Mon, 23 Feb 2026 12:47:37 -0800 Subject: [PATCH 08/13] integration test - all questions private to another student --- .../server/test/asyncQuestion.integration.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/server/test/asyncQuestion.integration.ts b/packages/server/test/asyncQuestion.integration.ts index d40dfece8..c83eff25f 100644 --- a/packages/server/test/asyncQuestion.integration.ts +++ b/packages/server/test/asyncQuestion.integration.ts @@ -1193,6 +1193,25 @@ describe('AsyncQuestion Integration', () => { ]), ); }); + it('returns no visible questions but hidden count > 0 when all questions are private to another student', async () => { + const studentUser4 = await UserFactory.create(); + await UserCourseFactory.create({ + user: studentUser4, + course, + role: Role.STUDENT, + }); + + asyncQuestion2.staffSetVisible = false; + await asyncQuestion2.save(); + + const response = await supertest({ userId: studentUser4.id }).get( + `/asyncQuestions/${course.id}`, + ); + + expect(response.status).toBe(200); + expect(response.body.questions).toEqual([]); + expect(response.body.hiddenPrivateQuestionsCount).toBe(3); + }); it('does not show sensitive information for anonymous comments on questions unless they are the creator or staff', async () => { const comment1 = await AsyncQuestionCommentFactory.create({ question: asyncQuestion2, From 508eb23b33c88723f3ada19dc6fa708625e51c88 Mon Sep 17 00:00:00 2001 From: kjassani Date: Mon, 23 Feb 2026 12:48:19 -0800 Subject: [PATCH 09/13] integration test - not include tadeleted question --- .../server/test/asyncQuestion.integration.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/packages/server/test/asyncQuestion.integration.ts b/packages/server/test/asyncQuestion.integration.ts index c83eff25f..33c0d9968 100644 --- a/packages/server/test/asyncQuestion.integration.ts +++ b/packages/server/test/asyncQuestion.integration.ts @@ -1212,6 +1212,28 @@ describe('AsyncQuestion Integration', () => { expect(response.body.questions).toEqual([]); expect(response.body.hiddenPrivateQuestionsCount).toBe(3); }); + it('does not include TADeleted questions in hidden private count', async () => { + const studentUser4 = await UserFactory.create(); + await UserCourseFactory.create({ + user: studentUser4, + course, + role: Role.STUDENT, + }); + + asyncQuestion2.staffSetVisible = false; + await asyncQuestion2.save(); + + asyncQuestion3.status = asyncQuestionStatus.TADeleted; + await asyncQuestion3.save(); + + const response = await supertest({ userId: studentUser4.id }).get( + `/asyncQuestions/${course.id}`, + ); + + expect(response.status).toBe(200); + expect(response.body.questions).toEqual([]); + expect(response.body.hiddenPrivateQuestionsCount).toBe(2); + }); it('does not show sensitive information for anonymous comments on questions unless they are the creator or staff', async () => { const comment1 = await AsyncQuestionCommentFactory.create({ question: asyncQuestion2, From f337a23ff87c8a6548df002072b5e101293d9b72 Mon Sep 17 00:00:00 2001 From: kjassani Date: Mon, 23 Feb 2026 12:49:06 -0800 Subject: [PATCH 10/13] integration test do not include author's private question in hidden count --- .../server/test/asyncQuestion.integration.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/packages/server/test/asyncQuestion.integration.ts b/packages/server/test/asyncQuestion.integration.ts index 33c0d9968..86896c811 100644 --- a/packages/server/test/asyncQuestion.integration.ts +++ b/packages/server/test/asyncQuestion.integration.ts @@ -1234,6 +1234,22 @@ describe('AsyncQuestion Integration', () => { expect(response.body.questions).toEqual([]); expect(response.body.hiddenPrivateQuestionsCount).toBe(2); }); + it("does not include the requester's own private questions in hidden private count", async () => { + asyncQuestion2.staffSetVisible = false; + await asyncQuestion2.save(); + + const response = await supertest({ userId: studentUser2.id }).get( + `/asyncQuestions/${course.id}`, + ); + + expect(response.status).toBe(200); + expect(response.body.hiddenPrivateQuestionsCount).toBe(2); + expect(response.body.questions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: asyncQuestion3.id }), + ]), + ); + }); it('does not show sensitive information for anonymous comments on questions unless they are the creator or staff', async () => { const comment1 = await AsyncQuestionCommentFactory.create({ question: asyncQuestion2, From c362d3e9a9d49194e3ffb6edf088e1db78df4f08 Mon Sep 17 00:00:00 2001 From: Adam Fipke Date: Tue, 24 Feb 2026 14:32:25 -0800 Subject: [PATCH 11/13] modified 'Convert to Anytime Question' text and styling to try to reduce cognitive load as much as possible. Also added a 'Continue Navigation' popconfirm since I imagine people can accidentally click on it easily --- .../[cid]/components/chatbot/Chatbot.tsx | 34 ++++++++++++++----- 1 file changed, 25 insertions(+), 9 deletions(-) diff --git a/packages/frontend/app/(dashboard)/course/[cid]/components/chatbot/Chatbot.tsx b/packages/frontend/app/(dashboard)/course/[cid]/components/chatbot/Chatbot.tsx index 35dbfcd5a..ef5c88b31 100644 --- a/packages/frontend/app/(dashboard)/course/[cid]/components/chatbot/Chatbot.tsx +++ b/packages/frontend/app/(dashboard)/course/[cid]/components/chatbot/Chatbot.tsx @@ -40,7 +40,7 @@ import { import { API } from '@/app/api' import MarkdownCustom from '@/app/components/Markdown' import Link from 'next/link' -import { usePathname } from 'next/navigation' +import { usePathname, useRouter } from 'next/navigation' import { Message, parseThinkBlock, @@ -99,6 +99,7 @@ const Chatbot: React.FC = ({ const messagesEndRef = useRef(null) const hasAskedQuestion = useRef(false) // to track if the user has asked a question const pathname = usePathname() + const router = useRouter() const currentPageTitle = convertPathnameToPageName(pathname) const [popResetOpen, setPopResetOpen] = useState(false) // used to temporarily store what question type the user is trying to change to @@ -558,16 +559,31 @@ const Chatbot: React.FC = ({ courseFeatures.asyncQueueEnabled && chatbotQuestionType === 'Course' && messages.length > 1 && ( -
- Discuss or verify this with your professor/TA:{' '} - + Want to discuss or verify this with your Professor/TA?{' '} + + trigger.parentNode as HTMLElement + } + onConfirm={() => { + router.push( + `/course/${cid}/async_centre?convertChatbotQ=true`, + ) }} > - Convert to Anytime Question - + { + e.preventDefault() + }} + > + Convert to Anytime Question + +
)} From e2943f8918e85b33942fbeaf90f5db13b984e345 Mon Sep 17 00:00:00 2001 From: Adam Fipke Date: Tue, 24 Feb 2026 14:57:13 -0800 Subject: [PATCH 12/13] fix so student deleted questions aren't counted as a hidden private question --- packages/server/src/asyncQuestion/asyncQuestion.controller.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/src/asyncQuestion/asyncQuestion.controller.ts b/packages/server/src/asyncQuestion/asyncQuestion.controller.ts index f52e6ee8a..0335bce78 100644 --- a/packages/server/src/asyncQuestion/asyncQuestion.controller.ts +++ b/packages/server/src/asyncQuestion/asyncQuestion.controller.ts @@ -827,7 +827,9 @@ export class asyncQuestionController { return { question: undefined, - isHiddenPrivate: question.status !== asyncQuestionStatus.TADeleted, + isHiddenPrivate: + question.status !== asyncQuestionStatus.TADeleted && + question.status !== asyncQuestionStatus.StudentDeleted, }; }), ); From 93c853a12ac56ff9d9f7022ce90ea9f726dede19 Mon Sep 17 00:00:00 2001 From: Adam Fipke Date: Tue, 24 Feb 2026 15:00:00 -0800 Subject: [PATCH 13/13] adjusted some Empty text slightly, centered the 'addiontal private questions', and added a tooltip explaining it. Also converted the condition variables to be inside the JSX since it's very slightly easier to maintain when everything's together --- .../course/[cid]/async_centre/page.tsx | 83 ++++++++++--------- .../modals/PromptStudentToLeaveQueueModal.tsx | 2 +- 2 files changed, 47 insertions(+), 38 deletions(-) diff --git a/packages/frontend/app/(dashboard)/course/[cid]/async_centre/page.tsx b/packages/frontend/app/(dashboard)/course/[cid]/async_centre/page.tsx index c30fd20d9..87ef5a9c7 100644 --- a/packages/frontend/app/(dashboard)/course/[cid]/async_centre/page.tsx +++ b/packages/frontend/app/(dashboard)/course/[cid]/async_centre/page.tsx @@ -226,17 +226,6 @@ export default function AsyncCentrePage( return displayedQuestions.slice(startIndex, endIndex) }, [page, pageSize, displayedQuestions]) - const showHiddenPrivateQuestionsNotice = - !isStaff && - hiddenPrivateQuestionsCount > 0 && - paginatedQuestions.length > 0 && - isLastPage - - const showNoQuestionsPostedEmpty = - (asyncQuestions?.length ?? 0) === 0 && hiddenPrivateQuestionsCount === 0 - const showNoPublicOrMineEmpty = - (asyncQuestions?.length ?? 0) === 0 && hiddenPrivateQuestionsCount > 0 - // This endpoint will be called to update unread count back to 0 when this page is entered // May seem more inefficient but this is the only way to ensure that the unread count is accurate given that userInfo no longer tracks it useEffect(() => { @@ -364,7 +353,8 @@ export default function AsyncCentrePage( return } else if ( asyncQuestionsResponse === undefined || - asyncQuestionsResponse === null + asyncQuestionsResponse === null || + asyncQuestions === undefined // should always be defined beyond this point, just for type safety ) { return } else { @@ -472,35 +462,54 @@ export default function AsyncCentrePage( /> ))} - {showNoQuestionsPostedEmpty && ( + {asyncQuestions.length === 0 && + hiddenPrivateQuestionsCount === 0 ? (
+ ) : ( + asyncQuestions.length === 0 && + hiddenPrivateQuestionsCount > 0 && ( +
+ +

+ No public questions or questions you created found. + Try posting a course question! +

+ {hiddenPrivateQuestionsCount > 0 && ( + +

+ +{hiddenPrivateQuestionsCount} additional + private question + {hiddenPrivateQuestionsCount === 1 ? '' : 's'} +

+
+ )} +
+ } + /> + + ) )} - {showNoPublicOrMineEmpty && ( -
- -

- No public or questions you created found -

-

- +{hiddenPrivateQuestionsCount} additional private - question - {hiddenPrivateQuestionsCount === 1 ? '' : 's'} -

-
- } - /> - - )} - {showHiddenPrivateQuestionsNotice && ( -

- +{hiddenPrivateQuestionsCount} additional private question - {hiddenPrivateQuestionsCount === 1 ? '' : 's'} -

- )} + { + // Show how many total private questions that the student can't see so that the student has + // a better way to know that this system is being used + // (and students usually ask questions to the most-popular system the prof set up) + !isStaff && + hiddenPrivateQuestionsCount > 0 && + paginatedQuestions.length > 0 && + isLastPage && ( + +

+ +{hiddenPrivateQuestionsCount} additional private + question + {hiddenPrivateQuestionsCount === 1 ? '' : 's'} +

+
+ ) + } {totalQuestions > 0 && ( diff --git a/packages/frontend/app/(dashboard)/course/[cid]/queue/[qid]/components/modals/PromptStudentToLeaveQueueModal.tsx b/packages/frontend/app/(dashboard)/course/[cid]/queue/[qid]/components/modals/PromptStudentToLeaveQueueModal.tsx index 2be25efa6..155aa31de 100644 --- a/packages/frontend/app/(dashboard)/course/[cid]/queue/[qid]/components/modals/PromptStudentToLeaveQueueModal.tsx +++ b/packages/frontend/app/(dashboard)/course/[cid]/queue/[qid]/components/modals/PromptStudentToLeaveQueueModal.tsx @@ -188,7 +188,7 @@ const PromptStudentToLeaveQueueModal: React.FC< icon={} size="large" > - Convert to anytime question + Convert to Anytime Question