Repository navigation
[Feat/#111] 내 내역 확인 화면 (열린피드백 작성내역·학생회비 납부내역·사물함 배정 상태) - #113
sangrae2325 wants to merge 17 commits into
Conversation
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configuration
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 |
| <LockersLayoutRenderer | ||
| layout={detail.layout} | ||
| lockers={lockers} | ||
| selectedLockerNumber={null} |
There was a problem hiding this comment.
[c] 지금은 하단 바의 사물함 이름은 배정 정보(entities/lockers)에서 오고, 지도에서 내 칸 표시는 구역 목데이터의 isMine에서 와서 "내 칸"을 정하는 곳이 두 군데인 것 같습니다. 그래서 둘을 맞추려고 lockersSectionDetails.ts에 MY_LOCKER_LABEL = "A-7"이 하나 더 생긴 것 같아요. 실제 API가 붙어서 두 값이 어긋나면, 하단 바와 지도 표시가 서로 다른 칸을 가리킬 수 있을 것 같습니다.
배정 정보의 칸을 selectedLockerNumber로 넘기면 출처가 하나로 합쳐질 것 같은데 어떨까요?
const myLocker = detail.lockers.find(
(locker) => locker.lockerLabel === assignment.lockerLabel,
);
<LockersLayoutRenderer
layout={detail.layout}
lockers={lockers}
selectedLockerNumber={myLocker?.lockerNumber ?? null}
/>이렇게 하면 LockersCell의 보기 전용 분기(isViewOnly ? locker.isMine : isSelected)도 필요 없어지고, createMockLockers의 isMine을 바꾸지 않아도 돼서 신청 화면에서 A-7이 내 칸으로 보이는 부작용도 사라질 것 같습니다.
| onViewLocation: () => void; | ||
| } | ||
|
|
||
| const WEEKDAYS = ["일", "월", "화", "수", "목", "금", "토"]; |
There was a problem hiding this comment.
[c] WEEKDAYS와 formatDate가 LockersApplyCompleteScreen.tsx에 있는 코드와 똑같아서, 공용 위치로 빼면 좋을 것 같습니다. 이번 PR에서 날짜 포맷이 화면마다 조금씩 갈리는 것 같아요.
LockersAssignmentContent:2026.09.05(토),2026.09.05(토) 13:59FeePaymentCard: 이름은 같은formatDateTime인데 요일 없이2026.09.04 11:49FeedbacksHistoryCard: 목록은2026.04.25FeedbacksMineDetailContent: 상세는 원본 그대로2026-04-25(50·86번째 줄)
// lib/date.ts
export function formatDate(date: string, options?: { weekday?: boolean }): string { ... }
export function formatDateTime(value: string, options?: { weekday?: boolean }): string { ... }한곳에 모아두면 이름이 같은데 결과가 다른 일도 없어질 것 같습니다. 특히 작성내역 목록과 상세의 날짜 표기가 다른 건 Figma와 맞는지 한번 확인 부탁드립니다!
There was a problem hiding this comment.
(cc2a03d) lib/date.ts로 모았습니다! 작성내역 상세의 2026-04-25는 Figma에서 하이픈으로 작성내역 목록과 표기가 달라서 그대로 뒀습니다!
| type BadgeColor = "orange" | "red" | "green"; | ||
|
|
||
| // 홈 내 정보 목록(HomeInfoList)의 학생회비 배지와 같은 이름·색이다 | ||
| export const FEE_STATUS_BADGES: Record< |
There was a problem hiding this comment.
[c] FEE_STATUS_BADGES가 HomeInfoList.tsx의 FEE_BADGES와 내용이 완전히 같은 것 같습니다(주석에도 같다고 적어주셨고요). 상태 타입도 홈은 HomeFeeStatus(homeMock), 납부내역은 FeePaymentStatus(entities/fee)로 두 벌이 돼서, 나중에 상태가 하나 추가되거나 문구가 바뀌면 한쪽을 빠뜨리기 쉬울 것 같아요.
홈이 entities/fee의 타입과 이 매핑을 가져다 쓰도록 맞추면 어떨까요? "배정완료" 배지도 HomeInfoList의 LOCKER_BADGES와 LockersAssignmentContent에 따로 하드코딩돼 있어서 같은 방식으로 맞추면 좋을 것 같습니다.
| const navigate = useNavigate(); | ||
|
|
||
| // 바로 들어와 뒤로 갈 히스토리가 없으면 홈으로 보낸다 | ||
| const goBack = () => { |
There was a problem hiding this comment.
[c] history.state.idx로 뒤로 갈 곳이 없는지 확인하는 goBack이 이번 PR에서 새 화면 5개(FeePaymentsScreen·FeedbacksMineScreen·FeedbacksMineDetailScreen·LockersAssignmentScreen·LockersMineScreen)에 똑같이 들어가서, main과 합치면 10개 화면에 같은 코드가 생기는 것 같습니다. 대비 경로만 다르고 나머지는 같아서 훅으로 빼면 좋을 것 같아요.
// hooks/useBackNavigation.ts
export function useBackNavigation(fallbackPath: string) {
const navigate = useNavigate();
return () => {
const historyIndex = (window.history.state as { idx?: number } | null)?.idx;
if (historyIndex !== undefined && historyIndex > 0) {
navigate(-1);
return;
}
navigate(fallbackPath, { replace: true });
};
}기존 화면까지 바꾸는 건 범위가 커지니, 이번에는 새 화면 5개만 이 훅을 쓰고 나머지는 별도 이슈로 옮기는 방법도 괜찮을 것 같습니다. #97 리뷰에도 같은 의견을 남겨뒀습니다!
| />, | ||
| ); | ||
|
|
||
| const openKakaoChannel = () => { |
There was a problem hiding this comment.
[c] 카카오톡 채널 주소가 비어 있어도 "문의하기" 버튼은 그대로 그려져서, 지금은 눌러도 반응이 없는 버튼이 되는 것 같습니다. 주소가 정해질 때까지 버튼을 숨기거나, 주소가 있을 때만 onContact를 넘기는 쪽이 사용자 입장에서 덜 헷갈릴 것 같아요.
그리고 외부 링크를 여는 코드가 #97(의견 보내기·개인정보 처리방침)에도 생겼는데, 여기는 "noopener", 거기는 "noopener,noreferrer"로 옵션이 다릅니다. 앱 WebView에서 외부 링크를 어떻게 열지 정해야 하는 것도 같아서, lib/ 쪽에 함수 하나로 모아두면 좋을 것 같습니다.
| const feedback = use(fetchMyFeedback(feedbackId)); | ||
|
|
||
| if (!feedback) { | ||
| return <Navigate replace to="/my/feedbacks" />; |
There was a problem hiding this comment.
[a] 데이터 컴포넌트가 <Navigate replace to="/my/feedbacks" />로 이동할 경로를 직접 알고 있는 것 같습니다(LockersMineContent.tsx:36도 같아요). 기존에는 <Navigate>를 화면(Screen) 파일에서만 쓰고, 데이터 컴포넌트는 이동을 콜백 prop으로 받는 규칙이라 경로는 화면이 넘겨주면 좋을 것 같아요.
// FeedbacksMineDetailScreen
<FeedbacksMineDetailContent fallbackPath={...} ... />
// FeedbacksMineDetailContent
if (!feedback) {
return <Navigate replace to={fallbackPath} />;
}같은 경로가 화면의 goBack 대비 경로와도 겹치고, /my/feedbacks·/my/locker 문자열이 4~5곳에 흩어져 있습니다. 다음 이슈에서 홈과 연결할 때를 생각하면 NOTIFICATIONS_PATH처럼 경로 상수로 빼두면 좋을 것 같습니다.
| paidAt: string; | ||
| confirmedAt: string | null; | ||
| // 확인필요일 때 학생회가 남긴 확인 내용 | ||
| reviewNote?: string; |
There was a problem hiding this comment.
[a] reviewNote가 사실상 "확인필요"일 때만 오는 값이라면, 판별 유니온으로 나누면 FeePaymentCard.tsx:71에서 status와 reviewNote를 두 번 확인하지 않아도 될 것 같습니다.
interface FeePaymentBase {
id: string;
title: string;
amount: number | null;
paidAt: string;
confirmedAt: string | null;
}
interface FeePaymentDefault extends FeePaymentBase {
status: "checking" | "paid";
}
interface FeePaymentNeedsCheck extends FeePaymentBase {
status: "needs-check";
reviewNote: string;
}
export type FeePayment = FeePaymentDefault | FeePaymentNeedsCheck;서버에서 확인필요인데 reviewNote가 비어서 올 수도 있다면 지금처럼 두셔도 괜찮을 것 같아요!
| feedback, | ||
| onSelect, | ||
| }: FeedbacksHistoryCardProps) { | ||
| const isAnswered = feedback.answer !== undefined; |
There was a problem hiding this comment.
[a] 답변이 달렸는지 판단하는 기준이 목록 카드는 answer !== undefined, 상세(FeedbacksMineDetailContent.tsx:91)는 값이 있는지로 달라서, answer가 빈 문자열로 오면 목록은 "답변완료", 상세는 "답변 준비 중"으로 엇갈릴 것 같습니다. 판단을 함수 하나로 모으거나, 둘 중 한 기준으로 맞추면 좋을 것 같아요.
| const assignment = use(fetchMyLockerAssignment()); | ||
|
|
||
| if (!assignment) { | ||
| const year = new Date().getFullYear(); |
There was a problem hiding this comment.
[a] 사소한 것 세 가지 남깁니다!
- 빈 상태 그림을
icons/events/empty-events.svg에서 가져오고 있는데, 컨벤션상 두 기능 이상이 쓰면icons/common/으로 옮기는 규칙이 있어서 같이 옮기면 좋을 것 같습니다.FeedbacksHistoryCard의icons/home/arrow-right.svg도 마찬가지예요. - 다른 기능은 빈 상태를
<기능>EmptyState컴포넌트로 빼두었는데, 여기만 인라인으로 그려져 있어서LockersEmptyState로 빼면 구조가 맞을 것 같습니다. - 제목의 연도를
new Date().getFullYear()로 구해서 사용자 기기 시계를 따르게 되는 것 같아요. 학기 기준이라면 나중에 서버에서 받는 값으로 바꾸는 게 맞을 것 같습니다.
There was a problem hiding this comment.
아이콘 이동과 LockerEmptyState분리를 반영했습니다.(4d49c65) 연도는 서버 값으로 바꾸도록 남겨놨습니다!
| return mockupApi("feedbacks/mine", () => MY_FEEDBACKS_MOCK); | ||
| } | ||
|
|
||
| // 없는 피드백은 null — 상세 화면이 홈으로 보낸다. |
There was a problem hiding this comment.
[a] 주석에는 "상세 화면이 홈으로 보낸다"고 되어 있는데, 실제로는 작성내역 목록(/my/feedbacks)으로 보내고 있어서 주석만 맞춰주시면 좋을 것 같습니다!
…screens # Conflicts: # docs/conventions/wds-component-usage.md # src/features/feedbacks/FeedbacksListScreen.tsx
|
남겨주신 코멘트 모두 반영하고 일부 코멘트에 답글 남겨놓았습니다! |
#️⃣연관된 이슈
🎯 해결하려는 문제가 무엇인가요?
홈의 내 정보 목록(학생회비·열린피드백·사물함)에서 들어갈 화면이 없었다. Figma 홈 섹션(
3147:145181)의 내 내역 화면 3종을 목데이터로 구현한다./my/feedbacks3147:147748, 빈 상태3147:147818/my/feedbacks/:feedbackId3147:147756, 답변대기3147:147779/my/fee3147:147860, 확인필요3147:147879, 납부완료3147:147841/my/locker3147:148357, 빈 상태3147:148331/my/locker/location❓ 왜 해결해야 하나요?
홈에서 자기 납부·피드백·사물함 상태를 요약만 보고 자세한 내역을 확인할 곳이 없다. 홈 → 각 화면 라우터 연결 이슈를 하기 전에 들어갈 화면이 먼저 있어야 한다.
실제뷰
⭐ 어떻게 해결했나요?
1. 데이터는 main 규칙대로 (
entities/<도메인>+mockupApi+use()+ 화면별 스켈레톤)entities/feedbacks:fetchMyFeedbacks·fetchMyFeedback(id). 기존features/feedbacks/constants/feedbacks.ts의 피드백 타입·목데이터도 여기로 옮겨 한 곳에 둔다. 열린피드백 목록·상세·검색은 import 경로만 바뀌었다entities/fee:fetchFeePayments— 학기별 여러 건, 최신순entities/lockers:fetchMyLockerAssignment— 신청 안 했으면null*_MOCK_STATE값을 바꾸면 빈 상태·납부 상태별 화면을 볼 수 있다2. 열린피드백 작성내역 (
features/feedbacks)열린피드백 작성하기→/feedbacks/new해당 회차 답변 모아보기→/feedbacks/:id, 답변대기면 빈 상태 +열린피드백 보러 가기→/feedbacks**굵게**처리(renderAnswerText)를FeedbacksAnswerText로 꺼내 모아보기 상세와 같이 쓴다3. 학생회비 납부내역 (
features/fee, 신규)문의하기(학생회 카카오톡 채널)4. 사물함 배정 상태 + 위치 보기 (
features/lockers)사물함 위치 보기)LockersMapView로 꺼내 두 화면이 같이 쓴다5. 사물함 컴포넌트 이름 정리 (refactor 커밋)
Lockers뒤에Locker가 또 붙어 겹치던 이름을 정리했다. 동작 변화 없음LockersLockerCell·Grid·Legend·MinimapLockersCell·LockersGrid·LockersLegend·LockersMinimapLockersLockerSelectScreenLockersCellSelectScreen6. 문서 —
wds-component-usage.md에 내 내역 화면 WDS 매핑 절 추가🧩 이 PR의 한계 & 트레이드오프
문의하기는 아직 동작하지 않는다. 학생회 카카오톡 채널 주소를 받으면features/fee/constants/fee.ts의STUDENT_COUNCIL_KAKAO_CHANNEL_URL만 채우면 된다3147:148379)은 뺐다. 신청하면 바로 확정이라 이 상태가 없다use()+ 스켈레톤이 아니다. 데이터 위치만 옮겼고, 전환은 "데이터 로딩 규칙 적용" 이슈에서 사물함·홈 등과 같이 한다⛓️ 기존 기능에 미치는 영향
LockersMapView로 옮기고 파일 이름을 바꿈. 칸 70개 모두 선택 가능, 내 칸 초록 테두리 그대로인 것을 확인했다isMine이 되어, 신청 화면에서도 A-7이 내 칸(초록 테두리, 선택 불가)으로 보인다🔀 Edge Case & 실패 시나리오
-📋 검토한 대안과 선택 이유
LockersMapView로 꺼내, 신청 화면의 확인 모달·제출 로직이 보기 화면에 섞이지 않게 했다features/my에 모으기 vs 도메인별 폴더: 데이터·컴포넌트를 도메인과 같이 두려고feedbacks·fee·lockers에 나눴다entities/feedbacks로 합쳤다💬 리뷰 포인트
[c]사물함 칸 이름 정리(LockersCell등)와LockersMapView분리 — 칸 선택 화면 동작이 그대로인지[c]피드백 데이터 이전 후 열린피드백 목록·상세·검색이 그대로인지[a]위치 보기 화면의 범례·처음 스크롤 동작