Skip to content

[Feat/#111] 내 내역 확인 화면 (열린피드백 작성내역·학생회비 납부내역·사물함 배정 상태) - #113

Open
sangrae2325 wants to merge 17 commits into
mainfrom
feat/#111-my-history-screens
Open

sangrae2325 wants to merge 17 commits into
mainfrom
feat/#111-my-history-screens

Conversation

@sangrae2325

@sangrae2325 sangrae2325 commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

#️⃣연관된 이슈

🎯 해결하려는 문제가 무엇인가요?

홈의 내 정보 목록(학생회비·열린피드백·사물함)에서 들어갈 화면이 없었다. Figma 홈 섹션(3147:145181)의 내 내역 화면 3종을 목데이터로 구현한다.

화면 주소 Figma 상태
열린피드백 작성내역 /my/feedbacks 목록 3147:147748, 빈 상태 3147:147818
작성내역 상세 /my/feedbacks/:feedbackId 답변완료 3147:147756, 답변대기 3147:147779
학생회비 납부내역 /my/fee 납부확인중 3147:147860, 확인필요 3147:147879, 납부완료 3147:147841
사물함 배정 상태 /my/locker 배정완료 3147:148357, 빈 상태 3147:148331
내 사물함(위치 보기) /my/locker/location Figma 없음 — 칸 선택 화면을 보기 전용으로

❓ 왜 해결해야 하나요?

홈에서 자기 납부·피드백·사물함 상태를 요약만 보고 자세한 내역을 확인할 곳이 없다. 홈 → 각 화면 라우터 연결 이슈를 하기 전에 들어갈 화면이 먼저 있어야 한다.

실제뷰

image image image image image image

⭐ 어떻게 해결했나요?

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)

  • 카드: 답변완료(초록)·답변대기(주황) 배지, 질문 3줄 말줄임, 작성일
  • 빈 상태: 그림 + 문구 + 열린피드백 작성하기 → /feedbacks/new
  • 상세: 답변완료면 답변 + 해당 회차 답변 모아보기 → /feedbacks/:id, 답변대기면 빈 상태 + 열린피드백 보러 가기 → /feedbacks
  • 답변 글의 **굵게** 처리(renderAnswerText)를 FeedbacksAnswerText로 꺼내 모아보기 상세와 같이 쓴다

3. 학생회비 납부내역 (features/fee, 신규)

  • 납부 카드: 상태 배지(납부확인중 주황·확인필요 빨강·납부완료 초록), 납부금액·납부일시·확인일시
  • 확인필요 건은 카드 아래에 학생회 확인 내용 + 문의하기(학생회 카카오톡 채널)

4. 사물함 배정 상태 + 위치 보기 (features/lockers)

  • 배정완료: 학기·신청일시·사용기한 카드 + 배정된 사물함 카드(사물함 위치 보기)
  • 빈 상태: 그림 + "{올해} 사물함 신청내역이 없어요"
  • 위치 보기: 칸 선택 화면의 칸 배치·미니맵·확대를 그대로 쓰는 보기 전용 화면
    • 다른 칸은 누를 수 없고 내 칸만 선택된 칸 모양(파란 배경 + 흰 번호)
    • 헤더 "내 사물함" + 실제 사진 보기, 하단은 버튼 없이 "내 사물함 A-7"
    • 범례는 "내 사물함" 하나, 처음 열 때 내 칸이 화면 밖이면 보일 만큼만 스크롤
    • 칸 선택 화면의 미니맵·범례·확대 스크롤 코드를 LockersMapView로 꺼내 두 화면이 같이 쓴다

5. 사물함 컴포넌트 이름 정리 (refactor 커밋)

  • 도메인 접두사 Lockers 뒤에 Locker가 또 붙어 겹치던 이름을 정리했다. 동작 변화 없음
전 후
LockersLockerCell·Grid·Legend·Minimap LockersCell·LockersGrid·LockersLegend·LockersMinimap
LockersLockerSelectScreen LockersCellSelectScreen

6. 문서 — wds-component-usage.md에 내 내역 화면 WDS 매핑 절 추가

🧩 이 PR의 한계 & 트레이드오프

  • 홈에서 들어가는 길은 없다. 홈 내 정보 목록·현황 더보기 연결은 라우터 연결 이슈에서 한 번에 한다. 지금은 주소로 직접 들어가야 한다
  • 문의하기는 아직 동작하지 않는다. 학생회 카카오톡 채널 주소를 받으면 features/fee/constants/fee.ts의 STUDENT_COUNCIL_KAKAO_CHANNEL_URL만 채우면 된다
  • 사물함 번호는 A-7. Figma는 B-25지만 칸 배치 목데이터가 A-1·A-2 구역만 있어서 위치 보기가 열리도록 A-1 구역 칸으로 맞췄다
  • 배정대기중(3147:148379)은 뺐다. 신청하면 바로 확정이라 이 상태가 없다
  • 기존 열린피드백 목록·상세는 아직 use() + 스켈레톤이 아니다. 데이터 위치만 옮겼고, 전환은 "데이터 로딩 규칙 적용" 이슈에서 사물함·홈 등과 같이 한다
  • 요일은 날짜로 계산해서 Figma 표기(예: 2026.09.05(화))와 다를 수 있다

⛓️ 기존 기능에 미치는 영향

  • 열린피드백 목록·상세·검색: 피드백 데이터 import 경로만 바뀜. 화면은 그대로
  • 열린피드백 모아보기 상세: 답변 강조 렌더링을 공용 컴포넌트로 바꿈. 출력은 같다
  • 사물함 칸 선택: 미니맵·확대 코드를 LockersMapView로 옮기고 파일 이름을 바꿈. 칸 70개 모두 선택 가능, 내 칸 초록 테두리 그대로인 것을 확인했다
  • 사물함 목데이터 A-7이 isMine이 되어, 신청 화면에서도 A-7이 내 칸(초록 테두리, 선택 불가)으로 보인다

🔀 Edge Case & 실패 시나리오

  • 없는 피드백 id로 상세에 들어오면 작성내역 목록으로 보낸다
  • 배정이 없거나 배정 구역의 칸 배치가 없는데 위치 보기로 들어오면 배정 상태 화면으로 보낸다
  • 주소로 바로 들어와 뒤로 갈 기록이 없으면 홈(상세는 목록, 위치 보기는 배정 상태)으로 보낸다
  • 내 칸이 서버에서 "선택 불가"로 와도 위치 보기에서는 내 칸으로 그린다
  • 납부금액·확인일시가 없으면 -

📋 검토한 대안과 선택 이유

  • 위치 보기를 새로 그리기 vs 칸 선택 화면 재사용: Figma가 없고 칸 배치·확대 동작이 같아서 재사용했다. 화면을 통째로 모드 분기하지 않고 칸 영역만 LockersMapView로 꺼내, 신청 화면의 확인 모달·제출 로직이 보기 화면에 섞이지 않게 했다
  • 내 내역 화면을 features/my에 모으기 vs 도메인별 폴더: 데이터·컴포넌트를 도메인과 같이 두려고 feedbacks·fee·lockers에 나눴다
  • 피드백 목데이터를 두 곳에 두기 vs 한 곳으로 옮기기: 같은 피드백이 두 곳에 있으면 어긋나서 entities/feedbacks로 합쳤다

💬 리뷰 포인트

  • [c] 사물함 칸 이름 정리(LockersCell 등)와 LockersMapView 분리 — 칸 선택 화면 동작이 그대로인지
  • [c] 피드백 데이터 이전 후 열린피드백 목록·상세·검색이 그대로인지
  • [a] 위치 보기 화면의 범례·처음 스크롤 동작

@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6f3dd642-8417-4ffc-bd84-ce10e91fd83a
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

<LockersLayoutRenderer
layout={detail.layout}
lockers={lockers}
selectedLockerNumber={null}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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 = ["일", "월", "화", "수", "목", "금", "토"];

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[c] WEEKDAYS와 formatDate가 LockersApplyCompleteScreen.tsx에 있는 코드와 똑같아서, 공용 위치로 빼면 좋을 것 같습니다. 이번 PR에서 날짜 포맷이 화면마다 조금씩 갈리는 것 같아요.

  • LockersAssignmentContent: 2026.09.05(토), 2026.09.05(토) 13:59
  • FeePaymentCard: 이름은 같은 formatDateTime인데 요일 없이 2026.09.04 11:49
  • FeedbacksHistoryCard: 목록은 2026.04.25
  • FeedbacksMineDetailContent: 상세는 원본 그대로 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와 맞는지 한번 확인 부탁드립니다!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

(cc2a03d) lib/date.ts로 모았습니다! 작성내역 상세의 2026-04-25는 Figma에서 하이픈으로 작성내역 목록과 표기가 달라서 그대로 뒀습니다!

type BadgeColor = "orange" | "red" | "green";

// 홈 내 정보 목록(HomeInfoList)의 학생회비 배지와 같은 이름·색이다
export const FEE_STATUS_BADGES: Record<

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[c] FEE_STATUS_BADGES가 HomeInfoList.tsx의 FEE_BADGES와 내용이 완전히 같은 것 같습니다(주석에도 같다고 적어주셨고요). 상태 타입도 홈은 HomeFeeStatus(homeMock), 납부내역은 FeePaymentStatus(entities/fee)로 두 벌이 돼서, 나중에 상태가 하나 추가되거나 문구가 바뀌면 한쪽을 빠뜨리기 쉬울 것 같아요.

홈이 entities/fee의 타입과 이 매핑을 가져다 쓰도록 맞추면 어떨까요? "배정완료" 배지도 HomeInfoList의 LOCKER_BADGES와 LockersAssignmentContent에 따로 하드코딩돼 있어서 같은 방식으로 맞추면 좋을 것 같습니다.

Comment thread src/features/fee/FeePaymentsScreen.tsx Outdated
const navigate = useNavigate();

// 바로 들어와 뒤로 갈 히스토리가 없으면 홈으로 보낸다
const goBack = () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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 리뷰에도 같은 의견을 남겨뒀습니다!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

새 화면 5개에 훅 적용했습니다.(3f3ec57) 기존 화면은 #119 에서 처리하고 계셔서, 훅 파일을 #119 와 맞췄습니다! (d580a0a)

Comment thread src/features/fee/FeePaymentsScreen.tsx Outdated
/>,
);

const openKakaoChannel = () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[c] 카카오톡 채널 주소가 비어 있어도 "문의하기" 버튼은 그대로 그려져서, 지금은 눌러도 반응이 없는 버튼이 되는 것 같습니다. 주소가 정해질 때까지 버튼을 숨기거나, 주소가 있을 때만 onContact를 넘기는 쪽이 사용자 입장에서 덜 헷갈릴 것 같아요.

그리고 외부 링크를 여는 코드가 #97(의견 보내기·개인정보 처리방침)에도 생겼는데, 여기는 "noopener", 거기는 "noopener,noreferrer"로 옵션이 다릅니다. 앱 WebView에서 외부 링크를 어떻게 열지 정해야 하는 것도 같아서, lib/ 쪽에 함수 하나로 모아두면 좋을 것 같습니다.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

주소가 없으면 버튼을 숨기도록 반영했습니다. (be038dd) 링크를 여는 함수는 #97 이 머지되면 #97 에 맞추겠습니다!

const feedback = use(fetchMyFeedback(feedbackId));

if (!feedback) {
return <Navigate replace to="/my/feedbacks" />;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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처럼 경로 상수로 빼두면 좋을 것 같습니다.

Comment thread src/entities/fee/types.ts Outdated
paidAt: string;
confirmedAt: string | null;
// 확인필요일 때 학생회가 남긴 확인 내용
reviewNote?: string;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[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;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[a] 답변이 달렸는지 판단하는 기준이 목록 카드는 answer !== undefined, 상세(FeedbacksMineDetailContent.tsx:91)는 값이 있는지로 달라서, answer가 빈 문자열로 오면 목록은 "답변완료", 상세는 "답변 준비 중"으로 엇갈릴 것 같습니다. 판단을 함수 하나로 모으거나, 둘 중 한 기준으로 맞추면 좋을 것 같아요.

const assignment = use(fetchMyLockerAssignment());

if (!assignment) {
const year = new Date().getFullYear();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[a] 사소한 것 세 가지 남깁니다!

  • 빈 상태 그림을 icons/events/empty-events.svg에서 가져오고 있는데, 컨벤션상 두 기능 이상이 쓰면 icons/common/으로 옮기는 규칙이 있어서 같이 옮기면 좋을 것 같습니다. FeedbacksHistoryCard의 icons/home/arrow-right.svg도 마찬가지예요.
  • 다른 기능은 빈 상태를 <기능>EmptyState 컴포넌트로 빼두었는데, 여기만 인라인으로 그려져 있어서 LockersEmptyState로 빼면 구조가 맞을 것 같습니다.
  • 제목의 연도를 new Date().getFullYear()로 구해서 사용자 기기 시계를 따르게 되는 것 같아요. 학기 기준이라면 나중에 서버에서 받는 값으로 바꾸는 게 맞을 것 같습니다.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

아이콘 이동과 LockerEmptyState분리를 반영했습니다.(4d49c65) 연도는 서버 값으로 바꾸도록 남겨놨습니다!

Comment thread src/entities/feedbacks/feedbacksApi.ts Outdated
return mockupApi("feedbacks/mine", () => MY_FEEDBACKS_MOCK);
}

// 없는 피드백은 null — 상세 화면이 홈으로 보낸다.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

[a] 주석에는 "상세 화면이 홈으로 보낸다"고 되어 있는데, 실제로는 작성내역 목록(/my/feedbacks)으로 보내고 있어서 주석만 맞춰주시면 좋을 것 같습니다!

@sangrae2325

Copy link
Copy Markdown
Collaborator Author

남겨주신 코멘트 모두 반영하고 일부 코멘트에 답글 남겨놓았습니다!

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

내 내역 확인 화면 (열린피드백 작성내역·학생회비 납부내역·사물함 배정 상태)

2 participants