[Feat] WTH-436: 어드민 회비 관리 설정 페이지 구현#140
Hidden character warning
Conversation
…into feat/WTH-436-어드민-회비-설정-구현
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (4)
🚧 Files skipped from review as they are similar to previous changes (2)
📝 WalkthroughWalkthrough회비 설정 페이지, 거래 단건 상세 조회, 납부 상태 기반 선택 UI, 설정 단계 공통 헤더, 로딩 스켈레톤이 추가되었습니다. 거래 상세 모달은 메모와 복수 영수증을 표시하도록 확장됐고, 관리 화면의 일부 버튼과 레이아웃 스타일이 조정됐습니다. Changes회비 관리 기능 개편
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant DuesPageContent
participant useAdminDuesTransactionQuery
participant duesApi
participant TransactionDetailModal
DuesPageContent->>useAdminDuesTransactionQuery: selectedTransaction.id, detailOpen 전달
useAdminDuesTransactionQuery->>duesApi: getTransaction(clubId, accountId, transactionId)
duesApi-->>useAdminDuesTransactionQuery: TransactionItem 응답
DuesPageContent->>TransactionDetailModal: detailToTransactionDetail(transactionDetail) 전달
TransactionDetailModal->>TransactionDetailModal: memo / receipts 렌더링
sequenceDiagram
participant DuesPaymentStatusPageContent
participant DuesMemberPaymentTable
participant MemberSelectHeader
DuesMemberPaymentTable->>DuesPaymentStatusPageContent: onSelectionChange(selectedIds)
DuesPaymentStatusPageContent->>MemberSelectHeader: selectedCount, selectedStatus 전달
MemberSelectHeader->>DuesPaymentStatusPageContent: onMarkPaid/onRefund/onMarkUnpaid 호출
DuesPaymentStatusPageContent->>DuesMemberPaymentTable: selectedIds, onSelectionChange 전달
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
PR 테스트 결과✅ Jest: 통과 🎉 모든 테스트를 통과했습니다! |
PR 검증 결과✅ TypeScript: 통과 🎉 모든 검증을 통과했습니다! |
|
구현한 기능 Preview: https://weeth-h6mjhrncv-weethsite-4975s-projects.vercel.app |
PR E2E 테스트 결과✅ Playwright: 통과 🎉 E2E 테스트를 통과했습니다! |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
src/components/admin/dues/DuesGenerationFilter.tsx (1)
61-67: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
대신 간격 유틸리티 사용 고려.반응형 줄바꿈 로직(
br+ 조건부 span) 자체는 정상 동작하지만, tablet 이상에서 간격을 로 하드코딩하는 대신gap이나ml-*같은 Tailwind 유틸리티를 사용하는 편이 유지보수와 국제화 측면에서 더 낫습니다.♻️ 제안 예시
- <span className="typo-body1 text-text-alternative"> + <span className="typo-body1 tablet:inline-flex tablet:items-center tablet:gap-100 text-text-alternative"> 마지막 수정 <br className="tablet:hidden" /> - <span className="tablet:inline hidden"> </span> {lastUpdated ? formatLastUpdated(lastUpdated) : '-'} </span>🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/admin/dues/DuesGenerationFilter.tsx` around lines 61 - 67, The spacing between “마지막 수정” and the formatted date in DuesGenerationFilter should not rely on hardcoded non-breaking spaces. Update the JSX in DuesGenerationFilter to replace the conditional inline span with a Tailwind spacing utility such as gap or ml-* while keeping the existing responsive br behavior and the lastUpdated/formatLastUpdated rendering intact.src/components/admin/dues/DuesSettingPageContent.tsx (1)
42-66: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win로딩/미등록 상태 처리 누락
DuesPageContent는isPending으로 스켈레톤을,isDuesNotRegisteredError로 온보딩 모달을 노출하지만, 이 설정 페이지는dashboard/status/paymentTargets가 아직 로딩 중이거나 회비가 미등록인 경우에도 아무 가드 없이 0/빈 문자열 등 기본값으로 렌더링됩니다. 사용자가 로딩 중 또는 미등록 상태에서 직접 이 경로로 진입하면 실제로는 의미 없는 "빈" 설정 화면이 노출될 수 있습니다.같은 패턴(
DuesPageSkeleton,isDuesNotRegisteredError)을 재사용해 로딩/미등록 가드를 추가하는 것을 권장합니다.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/admin/dues/DuesSettingPageContent.tsx` around lines 42 - 66, The DuesSettingPageContent flow is missing guards for loading and unregistered dues states, so it can render a blank/default settings screen while dashboard/status/paymentTargets are still resolving or when dues are not registered. Reuse the same pattern as DuesPageContent by checking the relevant query pending state and isDuesNotRegisteredError, and show DuesPageSkeleton or the onboarding/modal fallback before using dashboard, status, or paymentTargets-derived values in DuesSettingPageContent.src/components/admin/dues/setup/components/SettingResultCardGrid.tsx (1)
108-108: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value동일한 삼항 조건 4회 반복 — 헬퍼로 추출 고려
onEditStep ? () => onEditStep(step) : undefined패턴이 4곳에서 반복됩니다. 작은 헬퍼 함수로 추출하면 중복을 줄일 수 있습니다.♻️ 제안
+ const editHandler = (step: number) => (onEditStep ? () => onEditStep(step) : undefined); + return ( <div className="grid grid-cols-2 gap-400"> {/* 기본 정보 */} - <InfoCard title="기본 정보" onEdit={onEditStep ? () => onEditStep(1) : undefined}> + <InfoCard title="기본 정보" onEdit={editHandler(1)}> ... {/* 이월 설정 */} - <InfoCard title="이월 설정" onEdit={onEditStep ? () => onEditStep(3) : undefined}> + <InfoCard title="이월 설정" onEdit={editHandler(3)}> ... {/* 납부 대상 */} - <InfoCard title="납부 대상" onEdit={onEditStep ? () => onEditStep(2) : undefined}> + <InfoCard title="납부 대상" onEdit={editHandler(2)}> ... {/* 계좌 공개 */} - <InfoCard title="계좌 공개" onEdit={onEditStep ? () => onEditStep(4) : undefined}> + <InfoCard title="계좌 공개" onEdit={editHandler(4)}>Also applies to: 115-115, 141-141, 162-162
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/admin/dues/setup/components/SettingResultCardGrid.tsx` at line 108, The onEdit callback logic in SettingResultCardGrid repeats the same ternary pattern multiple times, so extract it into a small helper inside the component (for example, a function that accepts the step and returns the proper handler or undefined). Update each InfoCard usage to call that helper instead of duplicating onEditStep ? () => onEditStep(step) : undefined, keeping the existing behavior intact.src/components/admin/dues/setup/components/SetupHeader.tsx (1)
5-5: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueProps 타입을 명명된 인터페이스로 분리 고려
파일 내 다른 컴포넌트들(
InfoCardProps,SettingResultCardGridProps등)은 명명된 Props 인터페이스를 export하는 패턴을 따릅니다.SetupHeader도SetupHeaderProps로 분리하면 재사용/테스트 시 일관성이 좋아집니다.♻️ 제안
-function SetupHeader({ cardinalNumber }: { cardinalNumber: number }) { +interface SetupHeaderProps { + cardinalNumber: number; +} + +function SetupHeader({ cardinalNumber }: SetupHeaderProps) {🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/components/admin/dues/setup/components/SetupHeader.tsx` at line 5, `SetupHeader` is using an inline props type instead of the named props-interface pattern used elsewhere in this file. Extract the `{ cardinalNumber: number }` shape into an exported `SetupHeaderProps` interface and update `SetupHeader` to use it so the component matches `InfoCardProps` and `SettingResultCardGridProps` for consistency and reuse.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/components/admin/dues/MemberSelectHeader.tsx`:
- Around line 45-49: MemberSelectHeader currently builds a single actions list
so 납부 정정, 환불 처리, and 납부 완료 are always shown regardless of selection state.
Update MemberSelectHeader to accept and use selectedStatus, and make the action
rendering conditional in the actions array so only paid/confirmed selections
expose 납부 정정 and 환불 처리, while only unpaid selections expose 납부 완료; alternatively
disable the irrelevant actions, but keep the behavior aligned with the bulk API
rules in onMarkUnpaid, onRefund, and onMarkPaid.
In `@src/components/admin/dues/modal/TransactionDetailModal.tsx`:
- Around line 58-90: `detailToTransactionDetail` is dropping detail-only fields,
so the modal loses the category suffix and registrant row when a full admin dues
transaction detail is available. Update the mapping used by
`useAdminDuesTransactionQuery` so `TransactionDetail` also carries `category`
and `registeredByName` from `detail`, and make sure `TransactionDetailModal`
continues to read those fields for `classificationLabel` and `registrant`.
In `@src/hooks/admin/useDuesVisibilityToggle.ts`:
- Around line 14-29: `useDuesVisibilityToggle` keeps `isPublic` in local state
with `useState(true)`, so when `accountId` changes within the same hook instance
the toggle can show a stale value. Update the hook to reset `isPublic` whenever
`accountId` changes and initialize it from the server-provided visibility once
that value is available, using the existing `useDuesVisibilityToggle` and
`handlePublicChange` flow so `DuesPageContent` and `DuesSettingPageContent`
always reflect the current account’s actual state.
---
Nitpick comments:
In `@src/components/admin/dues/DuesGenerationFilter.tsx`:
- Around line 61-67: The spacing between “마지막 수정” and the formatted date in
DuesGenerationFilter should not rely on hardcoded non-breaking spaces. Update
the JSX in DuesGenerationFilter to replace the conditional inline
span with a Tailwind spacing utility such as gap or ml-* while keeping the
existing responsive br behavior and the lastUpdated/formatLastUpdated rendering
intact.
In `@src/components/admin/dues/DuesSettingPageContent.tsx`:
- Around line 42-66: The DuesSettingPageContent flow is missing guards for
loading and unregistered dues states, so it can render a blank/default settings
screen while dashboard/status/paymentTargets are still resolving or when dues
are not registered. Reuse the same pattern as DuesPageContent by checking the
relevant query pending state and isDuesNotRegisteredError, and show
DuesPageSkeleton or the onboarding/modal fallback before using dashboard,
status, or paymentTargets-derived values in DuesSettingPageContent.
In `@src/components/admin/dues/setup/components/SettingResultCardGrid.tsx`:
- Line 108: The onEdit callback logic in SettingResultCardGrid repeats the same
ternary pattern multiple times, so extract it into a small helper inside the
component (for example, a function that accepts the step and returns the proper
handler or undefined). Update each InfoCard usage to call that helper instead of
duplicating onEditStep ? () => onEditStep(step) : undefined, keeping the
existing behavior intact.
In `@src/components/admin/dues/setup/components/SetupHeader.tsx`:
- Line 5: `SetupHeader` is using an inline props type instead of the named
props-interface pattern used elsewhere in this file. Extract the `{
cardinalNumber: number }` shape into an exported `SetupHeaderProps` interface
and update `SetupHeader` to use it so the component matches `InfoCardProps` and
`SettingResultCardGridProps` for consistency and reuse.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8cd7491d-3453-4d15-a685-5fd4581b236e
📒 Files selected for processing (28)
src/app/(private)/[clubId]/admin/dues/setting/page.tsxsrc/app/(private)/[clubId]/admin/layout.tsxsrc/components/admin/dues/DuesGenerationFilter.tsxsrc/components/admin/dues/DuesMemberPaymentTable.tsxsrc/components/admin/dues/DuesPageContent.tsxsrc/components/admin/dues/DuesPageSkeleton.tsxsrc/components/admin/dues/DuesPaymentStatusPageContent.tsxsrc/components/admin/dues/DuesPaymentSummaryCard.tsxsrc/components/admin/dues/DuesSettingPageContent.tsxsrc/components/admin/dues/DuesTopBar.tsxsrc/components/admin/dues/MemberSelectHeader.tsxsrc/components/admin/dues/index.tssrc/components/admin/dues/modal/TransactionDetailModal.tsxsrc/components/admin/dues/setup/DuesSetupStep1.tsxsrc/components/admin/dues/setup/DuesSetupStep2.tsxsrc/components/admin/dues/setup/DuesSetupStep3.tsxsrc/components/admin/dues/setup/DuesSetupStep4.tsxsrc/components/admin/dues/setup/DuesSetupStep5.tsxsrc/components/admin/dues/setup/components/SettingResultCardGrid.tsxsrc/components/admin/dues/setup/components/SetupHeader.tsxsrc/components/admin/dues/setup/components/index.tssrc/constants/admin/dues.constants.tssrc/hooks/admin/index.tssrc/hooks/admin/useDuesVisibilityToggle.tssrc/hooks/queries/admin/adminQueryKeys.tssrc/hooks/queries/admin/useAdminDuesQueries.tssrc/hooks/queries/admin/useDuesDashboardQuery.tssrc/lib/apis/adminDues.ts
| export function useDuesVisibilityToggle(clubId: string, accountId: number | null) { | ||
| const [isPublic, setIsPublic] = useState(true); | ||
|
|
||
| const { mutate: updateMemberVisibility } = useUpdateMemberVisibility(clubId, accountId, { | ||
| onError: () => toastError('공개 설정 변경에 실패했습니다.'), | ||
| }); | ||
|
|
||
| const handlePublicChange = (value: boolean) => { | ||
| setIsPublic(value); | ||
| updateMemberVisibility(value, { | ||
| onError: () => setIsPublic(!value), | ||
| }); | ||
| }; | ||
|
|
||
| return { isPublic, handlePublicChange }; | ||
| } |
There was a problem hiding this comment.
🔒 Security & Privacy | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the hook and its call sites
git ls-files | rg 'src/hooks/admin/useDuesVisibilityToggle\.ts|DuesPageContent\.tsx|DuesSettingPageContent\.tsx|useAdminDuesMutations\.ts|useAdminDuesMutations\.tsx|DuesGenerationFilter'
# Show an outline for the hook file
ast-grep outline src/hooks/admin/useDuesVisibilityToggle.ts --view expanded || true
# Read the hook and the referenced call sites in small slices
for f in \
src/hooks/admin/useDuesVisibilityToggle.ts \
src/components/admin/DuesPageContent.tsx \
src/components/admin/DuesSettingPageContent.tsx \
src/hooks/mutations/admin/useAdminDuesMutations.ts
do
if [ -f "$f" ]; then
echo "===== $f ====="
wc -l "$f"
cat -n "$f" | sed -n '1,220p'
fi
doneRepository: Team-Weeth/weeth-client
Length of output: 9843
🏁 Script executed:
#!/bin/bash
set -euo pipefail
for f in \
src/components/admin/dues/DuesPageContent.tsx \
src/components/admin/dues/DuesSettingPageContent.tsx \
src/components/admin/dues/DuesGenerationFilter.tsx
do
echo "===== $f ====="
wc -l "$f"
cat -n "$f" | sed -n '1,260p'
doneRepository: Team-Weeth/weeth-client
Length of output: 20268
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Search for member visibility / bank account visibility fields and related dashboard types
rg -n "member-visibility|memberVisibility|isPublic|bankAccountVisible|visible" src types . --glob '!**/*.map' --glob '!**/node_modules/**'
# Inspect the dashboard / dues response types if present
fd -a "dues" src types | sed -n '1,120p'Repository: Team-Weeth/weeth-client
Length of output: 15840
accountId 변경 시 isPublic을 초기화하세요
useState(true)만 쓰면 같은 훅 인스턴스에서 accountId가 바뀌어도 이전 기수의 토글 값이 그대로 남습니다. DuesPageContent/DuesSettingPageContent처럼 기수 전환이나 지연 로딩으로 accountId가 바뀌는 경로에서는 공개 여부 스위치가 실제 상태와 다르게 보일 수 있으니, 최소한 accountId 변경 시 상태를 리셋하고 서버 값이 생기면 그 값으로 초기화하는 쪽이 안전합니다.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/hooks/admin/useDuesVisibilityToggle.ts` around lines 14 - 29,
`useDuesVisibilityToggle` keeps `isPublic` in local state with `useState(true)`,
so when `accountId` changes within the same hook instance the toggle can show a
stale value. Update the hook to reset `isPublic` whenever `accountId` changes
and initialize it from the server-provided visibility once that value is
available, using the existing `useDuesVisibilityToggle` and `handlePublicChange`
flow so `DuesPageContent` and `DuesSettingPageContent` always reflect the
current account’s actual state.
PR 테스트 결과✅ Jest: 통과 🎉 모든 테스트를 통과했습니다! |
|
구현한 기능 Preview: https://weeth-ndx4j6krh-weethsite-4975s-projects.vercel.app |
PR 검증 결과✅ TypeScript: 통과 🎉 모든 검증을 통과했습니다! |
PR E2E 테스트 결과✅ Playwright: 통과 🎉 E2E 테스트를 통과했습니다! |
✅ PR 유형
어떤 변경 사항이 있었나요?
📌 관련 이슈번호
✅ Key Changes
🆕 회비 관리 설정 페이지 신규 구현
/[clubId]/admin/dues/setting라우트 추가 (DuesSettingPageContent)PaymentTargetModal)로 대상 편집 진입TopBar의 "설정" 버튼 → 설정 페이지로 이동 연결🔗 거래내역 상세 조회 API 연동
duesApi.getTransaction+useAdminDuesTransactionQuery추가 — 상세 모달이 열릴 때만(enabled게이팅) 목록에 없는 메모·영수증 파일 정보를 담은 단건 상세를 조회adminQueryKeys.duesTransaction쿼리 키 추가💀 대시보드 로딩 스켈레톤 추가
DuesPageSkeleton추가 — 기수가 선택된 상태에서 대시보드 로딩 중일 때 노출 (미등록 장부/기수 없음 케이스는 제외)👥 납부 대상 선택 UX 개선
disabled+ 안내 툴팁) — 벌크 액션이 일부 대상에게 무의미해지는 것 방지formatPhone으로 포맷 표시MemberSelectHeader컴포넌트로 분리 (납부 정정 / 환불 처리 / 납부 완료)♻️ 공개 여부 토글 훅 추출
useDuesVisibilityToggle추가 — 낙관적 업데이트(즉시 반영 후 실패 시 롤백) 로직을 훅으로 분리해 회비 대시보드/설정 페이지에서 공유🎛 설정 스텝 헤더 공통화 + 온보딩 안내
SetupHeader추가 — Step1~5 헤더를 통일하고 "다음으로 버튼을 눌러야 변경사항이 저장됩니다" 툴팁 노출SettingResultCardGrid의onEdit/onEditStep을 optional로 변경 — 미전달 시 수정 버튼을 숨겨 읽기 전용 모드 지원📱 모바일 반응형 대응
🚧 기타
DUES_NOT_EXIST_CODE(20100)추가📸 스크린샷 or 실행영상
🎸 기타 사항 or 추가 코멘트
Summary by CodeRabbit
New Features
Bug Fixes