Skip to content

refactor(fe): improve assignment/exercise accordion UI#3600

Open
minngyuseong wants to merge 7 commits into
mainfrom
t2719-improve-accordion-ui
Open

refactor(fe): improve assignment/exercise accordion UI#3600
minngyuseong wants to merge 7 commits into
mainfrom
t2719-improve-accordion-ui

Conversation

@minngyuseong
Copy link
Copy Markdown
Contributor

@minngyuseong minngyuseong commented May 29, 2026

안녕하세요 안녕하세요 반가워요 보고싶어요

course의 accordion 개선점이 많이 보여서 코드 수정하고 올려봅니다!

문제 사항 (Before)

  • 중복 컴포넌트: AssignmentAccordionExerciseAccordion이 거의 동일한 코드로 분리되어 있어 유지보수 어려움
  • 로딩 중 과제 없음: 아코디언 목록 진입 시 데이터 로딩 전 "내역 없음" 화면이 순간 노출
  • 아코디언 열 때 잠깐 내려옴: 왜인지 모르는 css가 있었음
  • 아코디언 열릴 때 깜빡임: 아코디언 펼칠 때 문제 목록이 로딩되는 동안 빈 화면 노출
  • 거대한 단일 파일: AssignmentAccordion.tsx 한 파일에 600줄 이상의 컴포넌트가 혼재
  • 과제 없음 UI 개션: contest에 있는 컴포넌트 갖다 썼어요!

변경 사항 (After)

  • ExerciseAccordion 제거 후 AssignmentAccordionisExercise prop으로 통합
  • Suspense + skeleton으로 목록 진입 시 로딩 상태 처리
  • 아코디언 펼칠 때 useSuspenseQuery + Suspense fallback으로 skeleton 표시
  • 파일 분리: AssignmentAccordionItem, AssignmentAccordionTrigger, AssignmentProblemList
  • EmptyStatePlaceholder 공통 컴포넌트 추출
  • grades/problems 쿼리를 자식 컴포넌트로 이동해 불필요한 prop drilling 제거

시연 영상 (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 개션

image image

minngyuseong and others added 6 commits May 29, 2026 17:35
…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>
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

useSuspenseQueries를 사용하기 위해 임포트를 수정합니다.

Suggested change
import { useSuspenseQuery } from '@tanstack/react-query'
import { useSuspenseQueries } from '@tanstack/react-query'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헐 좋아요

Comment on lines +23 to +49
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}
/>
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

독립적인 세 개의 쿼리(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}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

이 부분 혹시 기존 방식으로 한 번 롤백해서 확인해볼 수 있을까요??
프리뷰에서 시작 전 과제 문제를 받아오지 못하는 거 같은데, 원인이 확실치는 않지만.. 기존에 record/submission을 분리했던 게 혹시 실패하면 pass하도록 한 게 아닐까 싶어서요,, 아니라면 죄송합니다ㅜ

사진상 3번째, 4번째 과제입니다@!

Copy link
Copy Markdown
Contributor Author

@minngyuseong minngyuseong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

제미나이 요청 반영했어요!

AssignmentProblemRecord,
AssignmentSubmission
} from '@/types/type'
import { useSuspenseQuery } from '@tanstack/react-query'
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헐 좋아요

Comment on lines +23 to +49
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}
/>
)
}
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다!

@github-project-automation github-project-automation Bot moved this to Pending ✋ in Codedang May 29, 2026
@minngyuseong minngyuseong requested a review from seoeun9 May 29, 2026 10:55
@seoeun9 seoeun9 added the preview 이 라벨이 붙어있어야 프론트엔드 Preview 환경이 생성됩니다 label May 29, 2026
@skkuding-bot
Copy link
Copy Markdown

skkuding-bot Bot commented May 29, 2026

Syncing Preview App Succeeded

Application: frontend
Revision: b0d66fdd1028167e29c8758bd6ea250b15ee37d7
Health Status: Healthy

Open Preview | View in Argo CD

Copy link
Copy Markdown
Contributor

@seoeun9 seoeun9 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그가 돌아왔다~~
고생하셨습니다!! 바뀐 아코디언 넘 깔끔하고 이쁘네여 👏👏

const grade = grades.find((g) => g.id === assignment.id)

const submittedCount = grade?.submittedCount ?? 0
const problemCount = grade?.problemCount ?? 0
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기 fallback 을 추가해야 할 것 같아요..! 시작 전인 과제에서 import된 문제가 있음에도 grade가 없어 0/0 으로 보이게 되는 거 같습니다 (instructor 에서 확인했습니다)

const problemCount = grade?.problemCount ?? problems?.total ?? 0 기존처럼 이런 느낌으로..

Comment on lines +23 to +49
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}
/>
)
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Image

이 부분 혹시 기존 방식으로 한 번 롤백해서 확인해볼 수 있을까요??
프리뷰에서 시작 전 과제 문제를 받아오지 못하는 거 같은데, 원인이 확실치는 않지만.. 기존에 record/submission을 분리했던 게 혹시 실패하면 pass하도록 한 게 아닐까 싶어서요,, 아니라면 죄송합니다ㅜ

사진상 3번째, 4번째 과제입니다@!

@dayeoni
Copy link
Copy Markdown
Contributor

dayeoni commented May 30, 2026

우와 대민규다 다시 입학하세요. 스꾸딩으로..

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

preview 이 라벨이 붙어있어야 프론트엔드 Preview 환경이 생성됩니다 🌊 squad-__init__ ⛳️ team-frontend

Projects

Status: Pending ✋

Development

Successfully merging this pull request may close these issues.

3 participants