refactor(fe): improve assignment/exercise accordion UI#3600
refactor(fe): improve assignment/exercise accordion UI#3600minngyuseong wants to merge 7 commits into
Conversation
…sExercise prop Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ding Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…mponent Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ner files Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…onItem Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… drilling - Add Suspense + skeleton for accordion content loading to prevent flickering - Extract AssignmentProblemListFetcher with useSuspenseQuery for lazy fetch - Move grades/problems queries to child components (React Query deduplication) - Create reusable EmptyStatePlaceholder component shared with RenderProblemList Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request refactors the assignment and exercise accordions by merging them into a single reusable AssignmentAccordion component, splitting it into smaller sub-components, and migrating to React Suspense with useSuspenseQuery. It also introduces a reusable EmptyStatePlaceholder component. The review feedback highlights a Suspense Waterfall issue in AssignmentProblemListFetcher that can be resolved by fetching data in parallel using useSuspenseQueries, as well as potential runtime errors due to missing optional chaining on record.problems[index] and grades.
| AssignmentProblemRecord, | ||
| AssignmentSubmission | ||
| } from '@/types/type' | ||
| import { useSuspenseQuery } from '@tanstack/react-query' |
| export function AssignmentProblemListFetcher({ | ||
| assignment, | ||
| isExercise | ||
| }: AssignmentProblemListFetcherProps) { | ||
| const { data: problems } = useSuspenseQuery( | ||
| assignmentProblemQueries.list({ | ||
| assignmentId: assignment.id, | ||
| groupId: Number(assignment.group.id) | ||
| }) | ||
| ) | ||
| const { data: record } = useSuspenseQuery( | ||
| assignmentQueries.record({ assignmentId: assignment.id }) | ||
| ) | ||
| const { data: submission } = useSuspenseQuery( | ||
| assignmentSubmissionQueries.summary({ assignmentId: assignment.id }) | ||
| ) | ||
|
|
||
| return ( | ||
| <AssignmentProblemList | ||
| problems={problems.data} | ||
| assignment={assignment} | ||
| isExercise={isExercise} | ||
| record={record} | ||
| submission={submission} | ||
| /> | ||
| ) | ||
| } |
There was a problem hiding this comment.
독립적인 세 개의 쿼리(problems, record, submission)를 각각 useSuspenseQuery로 호출하고 있어 Suspense Waterfall 현상이 발생합니다. 이로 인해 아코디언을 펼칠 때 각 쿼리가 순차적으로 실행되어 로딩 속도가 느려집니다.\n\n이 쿼리들은 서로 의존성이 없으므로 useSuspenseQueries를 사용하여 병렬로 데이터를 가져오도록 개선하는 것이 좋습니다.
export function AssignmentProblemListFetcher({\n assignment,\n isExercise\n}: AssignmentProblemListFetcherProps) {\n const [\n { data: problems },\n { data: record },\n { data: submission }\n ] = useSuspenseQueries({\n queries: [\n assignmentProblemQueries.list({\n assignmentId: assignment.id,\n groupId: Number(assignment.group.id)\n }),\n assignmentQueries.record({ assignmentId: assignment.id }),\n assignmentSubmissionQueries.summary({ assignmentId: assignment.id })\n ]\n })\n\n return (\n <AssignmentProblemList\n problems={problems.data}\n assignment={assignment}\n isExercise={isExercise}\n record={record}\n submission={submission}\n />\n )\n}
minngyuseong
left a comment
There was a problem hiding this comment.
제미나이 요청 반영했어요!
| AssignmentProblemRecord, | ||
| AssignmentSubmission | ||
| } from '@/types/type' | ||
| import { useSuspenseQuery } from '@tanstack/react-query' |
| export function AssignmentProblemListFetcher({ | ||
| assignment, | ||
| isExercise | ||
| }: AssignmentProblemListFetcherProps) { | ||
| const { data: problems } = useSuspenseQuery( | ||
| assignmentProblemQueries.list({ | ||
| assignmentId: assignment.id, | ||
| groupId: Number(assignment.group.id) | ||
| }) | ||
| ) | ||
| const { data: record } = useSuspenseQuery( | ||
| assignmentQueries.record({ assignmentId: assignment.id }) | ||
| ) | ||
| const { data: submission } = useSuspenseQuery( | ||
| assignmentSubmissionQueries.summary({ assignmentId: assignment.id }) | ||
| ) | ||
|
|
||
| return ( | ||
| <AssignmentProblemList | ||
| problems={problems.data} | ||
| assignment={assignment} | ||
| isExercise={isExercise} | ||
| record={record} | ||
| submission={submission} | ||
| /> | ||
| ) | ||
| } |
|
✅ Syncing Preview App Succeeded Application: |
seoeun9
left a comment
There was a problem hiding this comment.
그가 돌아왔다~~
고생하셨습니다!! 바뀐 아코디언 넘 깔끔하고 이쁘네여 👏👏
| const grade = grades.find((g) => g.id === assignment.id) | ||
|
|
||
| const submittedCount = grade?.submittedCount ?? 0 | ||
| const problemCount = grade?.problemCount ?? 0 |
There was a problem hiding this comment.
여기 fallback 을 추가해야 할 것 같아요..! 시작 전인 과제에서 import된 문제가 있음에도 grade가 없어 0/0 으로 보이게 되는 거 같습니다 (instructor 에서 확인했습니다)
const problemCount = grade?.problemCount ?? problems?.total ?? 0 기존처럼 이런 느낌으로..
| export function AssignmentProblemListFetcher({ | ||
| assignment, | ||
| isExercise | ||
| }: AssignmentProblemListFetcherProps) { | ||
| const { data: problems } = useSuspenseQuery( | ||
| assignmentProblemQueries.list({ | ||
| assignmentId: assignment.id, | ||
| groupId: Number(assignment.group.id) | ||
| }) | ||
| ) | ||
| const { data: record } = useSuspenseQuery( | ||
| assignmentQueries.record({ assignmentId: assignment.id }) | ||
| ) | ||
| const { data: submission } = useSuspenseQuery( | ||
| assignmentSubmissionQueries.summary({ assignmentId: assignment.id }) | ||
| ) | ||
|
|
||
| return ( | ||
| <AssignmentProblemList | ||
| problems={problems.data} | ||
| assignment={assignment} | ||
| isExercise={isExercise} | ||
| record={record} | ||
| submission={submission} | ||
| /> | ||
| ) | ||
| } |
|
우와 대민규다 다시 입학하세요. 스꾸딩으로.. |

안녕하세요 안녕하세요 반가워요 보고싶어요
course의 accordion 개선점이 많이 보여서 코드 수정하고 올려봅니다!
문제 사항 (Before)
AssignmentAccordion과ExerciseAccordion이 거의 동일한 코드로 분리되어 있어 유지보수 어려움AssignmentAccordion.tsx한 파일에 600줄 이상의 컴포넌트가 혼재변경 사항 (After)
ExerciseAccordion제거 후AssignmentAccordion에isExerciseprop으로 통합Suspense+ skeleton으로 목록 진입 시 로딩 상태 처리useSuspenseQuery+Suspensefallback으로 skeleton 표시AssignmentAccordionItem,AssignmentAccordionTrigger,AssignmentProblemListEmptyStatePlaceholder공통 컴포넌트 추출시연 영상 (Before, After)
로딩 중 과제 없음
Screen.Recording.2026-05-29.at.7.25.28.PM.mov
Screen.Recording.2026-05-29.at.7.27.16.PM.mov
아코디언 열 때 잠깐 내려옴 & 아코디언 열릴 때 깜빡임
Screen.Recording.2026-05-29.at.7.31.46.PM.mov
Screen.Recording.2026-05-29.at.7.32.23.PM.mov
과제 없음 UI 개션