Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
075e8da
feat: 내 내역 화면용 entities 추가와 피드백 데이터 이전
sangrae2325 Oct 9, 2026
36a40a7
feat: 열린피드백 작성내역 목록과 빈 상태 추가
sangrae2325 Oct 9, 2026
0d31055
feat: 열린피드백 작성내역 상세와 답변 강조 표시 공용화
sangrae2325 Oct 9, 2026
6bdc58f
feat: 학생회비 납부내역 화면 추가
sangrae2325 Oct 9, 2026
185027d
feat: 사물함 배정 상태 화면 추가
sangrae2325 Oct 9, 2026
fab21bb
feat: 사물함 위치 보기 화면 추가
sangrae2325 Oct 9, 2026
76582ab
refactor: 사물함 칸 컴포넌트 이름에서 겹치는 Locker 정리
sangrae2325 Oct 9, 2026
cee9761
docs: 내 내역 화면 WDS 매핑 정리
sangrae2325 Oct 9, 2026
1654772
Merge remote-tracking branch 'origin/main' into feat/#111-my-history-…
sangrae2325 Oct 14, 2026
545d660
refactor: 내 사물함 칸을 배정 정보 하나로 정하고 보기 전용 처리 정리
sangrae2325 Oct 14, 2026
cc2a03d
refactor: 날짜 표기 함수를 lib/date로 모음
sangrae2325 Oct 14, 2026
3480a0f
refactor: 학생회비·사물함 상태 배지와 타입을 한 곳으로 모음
sangrae2325 Oct 14, 2026
3f3ec57
refactor: 뒤로가기 훅과 내 내역 경로 상수 추가
sangrae2325 Oct 14, 2026
be038dd
fix: 채널 주소가 없을 때 문의하기를 숨기고 답변 여부 판단을 하나로 모음
sangrae2325 Oct 14, 2026
4d49c65
refactor: 공용 아이콘을 common으로 옮기고 사물함 빈 상태를 컴포넌트로 분리
sangrae2325 Oct 15, 2026
04f3706
docs: 내 내역 화면 문서에 바뀐 구조 반영
sangrae2325 Oct 15, 2026
d580a0a
refactor: 뒤로가기 훅을 #119와 같은 구현으로 맞춤
sangrae2325 Oct 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 17 additions & 2 deletions docs/conventions/wds-component-usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ WDS에 이름이 비슷한 `ProgressIndicator`가 실제로 존재해서(`제외

| 블록 | Figma | 코드 |
|---|---|---|
| `lockerGroup` | Zone Area + Locker Grid | `LockersLockerGrid`(`bordered`면 `Line/Solid/Alternative` 테두리 상자) |
| `lockerGroup` | Zone Area + Locker Grid | `LockersGrid`(`bordered`면 `Line/Solid/Alternative` 테두리 상자) |
| `label` | Direction Label·Aisle | `LockersMapLabel` — 세로면 한 글자씩 줄바꿈하고 단어 사이에 빈 줄 |
| `area` | Room Label·Zone Label·Stairs Area | `LockersMapArea` — 구역 선택 평면도와 같은 점선 상자 |
| `shelfLabel` | Shelf Label | `LockersShelfLabel` |
Expand Down Expand Up @@ -620,7 +620,22 @@ Figma는 헤더 배경이 없어서 배너 그림자(`Shadow/Spread/Small`, 60px

### 빈 상태는 WDS `FallbackView`가 아니라 홈 로컬 `HomeEmptyState`

일러스트 44px + 문구 한 줄 + 작은 버튼이라 행사 목록 Empty State 때와 같은 이유(`FallbackView`의 일러스트 폭·패딩 고정)로 맞지 않는다. 그림은 빌릴게 반납 빈 상태(`icons/bililge-empty/return-items.svg`)·행사 목록 빈 상태(`icons/events/empty-events.svg`)와 같은 도형이라 크기만 줄여 그대로 쓴다.
일러스트 44px + 문구 한 줄 + 작은 버튼이라 행사 목록 Empty State 때와 같은 이유(`FallbackView`의 일러스트 폭·패딩 고정)로 맞지 않는다. 그림은 빌릴게 반납 빈 상태(`icons/bililge-empty/return-items.svg`)·행사 목록 빈 상태(`icons/common/empty-events.svg`)와 같은 도형이라 크기만 줄여 그대로 쓴다.

## 내 내역 화면(열린피드백 작성내역 `3147:147748`·`3147:147818`·`3147:147756`·`3147:147779` / 학생회비 납부내역 `3147:147860`·`3147:147879`·`3147:147841` / 사물함 배정 상태 `3147:148357`·`3147:148331`) 구현 중 확정된 매핑

| Figma | 코드 | 확인 내용 |
|---|---|---|
| `Content Badge` (답변완료·답변대기, 납부확인중·확인필요·납부완료, 배정완료) | `ContentBadge size="small" variant="solid" color="accent"` | 홈 내 정보 목록과 같은 상태 이름·색이다. 초록 green, 주황 orange, 빨강 red. 상태→배지 매핑은 `FEE_STATUS_BADGES`(`features/fee/constants`)·`LOCKER_STATUS_BADGES`(`features/lockers/constants`) 한 곳에 두고 홈과 같이 쓴다 |
| 작성내역 상세 `해당 회차 답변 모아보기` | `Button color="primary" size="medium" variant="outlined"` | |
| 학생회비 `문의하기`, 사물함 `사물함 위치 보기` | `Button color="assistive" size="medium" variant="outlined" fullWidth` | 위치 보기의 꺾쇠는 `trailingContent={<IconChevronRight />}` |
| 빈 상태 버튼 `열린피드백 작성하기` / `열린피드백 보러 가기` | `Button color="assistive" size="small" variant="solid"` | 행사 목록 빈 상태(outlined)와 variant가 다르다 |
| 뒤로가기 헤더 | `ScreenHeader variant="normal"` + `TopNavigationButton` | `useBackNavigation(fallbackPath)`(`hooks/`) — 바로 들어와 뒤로 갈 기록이 없으면 홈(상세는 목록, 위치 보기는 배정 상태)으로 보낸다. 대비 경로는 각 기능 `constants`의 경로 상수(`FEE_PAYMENTS_PATH`·`MY_FEEDBACKS_PATH`·`LOCKER_ASSIGNMENT_PATH` 등)를 쓰고, 데이터가 없을 때의 `<Navigate>`도 화면이 넘긴 `fallbackPath`로 간다 |

- **카드·빈 상태는 Stream 로컬**: 작성내역 카드(`FeedbacksHistoryCard`), 납부 카드(`FeePaymentCard`), 배정 카드는 흰 바탕 `rounded-xl p-4` 로컬 카드다. 빈 상태는 행사 목록 Empty State와 같은 이유(`FallbackView`의 일러스트 크기·패딩 고정)로 로컬이다. 열린피드백은 `FeedbacksEmptyState`(그림 `icons/feedbacks/empty-feedback.svg`)를 쓰고, 사물함은 `LockersEmptyState`(그림은 행사 목록과 같은 `icons/common/empty-events.svg`)를 쓴다.
- **답변 강조 표시는 두 상세 화면이 공유한다**: 답변 글의 `**굵게**`를 볼드 `Typography`로 바꾸는 `FeedbacksAnswerText`를 모아보기 상세와 작성내역 상세가 같이 쓴다.
- **사물함 위치 보기(`/my/locker/location`)는 Figma에 없다**: 칸 선택 화면 배치를 보기 전용으로 쓴다. 칸 영역(미니맵·확대 스크롤)은 `LockersMapView`로 두 화면이 공유하고, 범례는 화면이 `legend`로 넘긴다(신청 `<LockersLegend />`, 위치 보기 `<LockersLegend variant="mine" />` — "내 사물함" 하나). 내 칸은 배정 정보(`lockerLabel`) 하나에서 정해서 `selectedLockerNumber`로 넘기고, 선택된 칸 모양(Primary/Normal 배경 + 흰 번호)으로 그린다. 보기 전용 여부는 `onSelect`가 없는지로만 판단하고, 이때 칸을 버튼으로 그리지 않는다. 처음 열 때 선택된 칸(`data-selected`)이 화면 밖이면 보일 만큼만 스크롤한다. 신청 화면의 내 칸(구역 목데이터 `isMine`, 초록 테두리, 선택 불가)은 보기 전용에서는 그리지 않는다.
- **Figma와 다르게 둔 값**: 요일은 날짜로 계산해서 Figma의 요일 표기와 다를 수 있다. 사물함 번호는 칸 배치 목데이터가 있는 A-1 구역에 맞춰 A-7로 두었다(Figma는 B-25). 사물함 빈 상태 문구의 연도는 기기 시계의 올해다(API 연동 때 서버 값으로 — 코드에 TODO). 날짜는 `lib/date`의 `formatDate`·`formatDateTime`으로 맞추고, 작성내역 상세만 Figma(`3147:147756`)대로 `2026-04-25` 하이픈 표기다.

## 알림(`3595:107100` 목록 / `3595:107112` empty / `3628:110837` 설정) 구현 중 확정된 매핑

Expand Down
45 changes: 43 additions & 2 deletions src/app/router.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,19 @@ import EventsApplicationCompleteScreen from "@/features/events/EventsApplication
import EventsApplicationScreen from "@/features/events/EventsApplicationScreen";
import EventsDetailScreen from "@/features/events/EventsDetailScreen";
import EventsListScreen from "@/features/events/EventsListScreen";
import FeePaymentsScreen from "@/features/fee/FeePaymentsScreen";
import FeedbacksDetailScreen from "@/features/feedbacks/FeedbacksDetailScreen";
import FeedbacksListScreen from "@/features/feedbacks/FeedbacksListScreen";
import FeedbacksMineDetailScreen from "@/features/feedbacks/FeedbacksMineDetailScreen";
import FeedbacksMineScreen from "@/features/feedbacks/FeedbacksMineScreen";
import FeedbacksNewScreen from "@/features/feedbacks/FeedbacksNewScreen";
import HomeScreen from "@/features/home/HomeScreen";
import LockersApplyCompleteScreen from "@/features/lockers/LockersApplyCompleteScreen";
import LockersApplyFailureScreen from "@/features/lockers/LockersApplyFailureScreen";
import LockersApplyScreen from "@/features/lockers/LockersApplyScreen";
import LockersLockerSelectScreen from "@/features/lockers/LockersLockerSelectScreen";
import LockersAssignmentScreen from "@/features/lockers/LockersAssignmentScreen";
import LockersCellSelectScreen from "@/features/lockers/LockersCellSelectScreen";
import LockersMineScreen from "@/features/lockers/LockersMineScreen";
import LockersSectionSelectScreen from "@/features/lockers/LockersSectionSelectScreen";
import NoticesDetailScreen from "@/features/notices/NoticesDetailScreen";
import NoticesListScreen from "@/features/notices/NoticesListScreen";
Expand Down Expand Up @@ -83,6 +88,42 @@ const routes = [
} satisfies ScreenRouteHandle,
path: "/feedbacks/:feedbackId",
},
{
element: <FeedbacksMineScreen />,
// 내 내역 화면 — 하단 탭 없이 회색 배경(기본값)
handle: { hasBottomNav: false } satisfies ScreenRouteHandle,
path: "/my/feedbacks",
},
{
element: <FeedbacksMineDetailScreen />,
// 작성내역 상세 — 열린피드백 상세와 같은 흰 배경, 하단 탭 없음
handle: {
background: "normal",
hasBottomNav: false,
} satisfies ScreenRouteHandle,
path: "/my/feedbacks/:feedbackId",
},
{
element: <FeePaymentsScreen />,
// 내 내역 화면 — 하단 탭 없이 회색 배경(기본값)
handle: { hasBottomNav: false } satisfies ScreenRouteHandle,
path: "/my/fee",
},
{
element: <LockersAssignmentScreen />,
// 내 내역 화면 — 하단 탭 없이 회색 배경(기본값)
handle: { hasBottomNav: false } satisfies ScreenRouteHandle,
path: "/my/locker",
},
{
element: <LockersMineScreen />,
// 칸 선택 화면과 같은 흰 배경
handle: {
background: "normal",
hasBottomNav: false,
} satisfies ScreenRouteHandle,
path: "/my/locker/location",
},
{
element: <EventsDetailScreen />,
// Bottom Nav 대신 하단 고정 버튼(Action Area)이 있는 화면
Expand Down Expand Up @@ -177,7 +218,7 @@ const routes = [
path: "/lockers/apply/sections",
},
{
element: <LockersLockerSelectScreen />,
element: <LockersCellSelectScreen />,
// 구역 선택 화면과 같다 — 하단 고정 선택 영역이 있고, 칸 배경이 흰 면 위에 놓인다
handle: {
background: "normal",
Expand Down
13 changes: 13 additions & 0 deletions src/assets/icons/feedbacks/empty-feedback.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/entities/fee/feeApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { FEE_PAYMENTS_MOCK } from "@/entities/fee/feeMock";
import type { FeePayment } from "@/entities/fee/types";
import { mockupApi } from "@/lib/mockupApi";

// 실 API가 붙으면 함수 안쪽만 요청 코드로 바꾼다 — 화면은 이 함수로만 데이터를 받는다.
export function fetchFeePayments(): Promise<FeePayment[]> {
return mockupApi("fee/payments", () => FEE_PAYMENTS_MOCK);
}
57 changes: 57 additions & 0 deletions src/entities/fee/feeMock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import type { FeePayment } from "@/entities/fee/types";

const TITLE = "소프트웨어융합대학 학생회비";

// Figma: 학생회비 납부내역 납부확인중 (nodeId 3147:147860), 확인필요 (3147:147879), 납부완료 (3147:147841)
const CHECKING: FeePayment = {
amount: 150000,
confirmedAt: null,
id: "2026-2",
paidAt: "2026-09-04T11:49",
status: "checking",
title: TITLE,
};

const NEEDS_CHECK: FeePayment = {
amount: null,
confirmedAt: null,
id: "2026-2",
paidAt: "2026-09-04T11:49",
reviewNote:
"입금자 정보를 확인하지 못했어요. 문제가 있을 경우 학생회에 문의해 주세요.",
status: "needs-check",
title: TITLE,
};

const PAID: FeePayment = {
amount: 150000,
confirmedAt: "2026-09-05T13:30",
id: "2026-2",
paidAt: "2026-09-04T11:49",
status: "paid",
title: TITLE,
};

// 여러 학기 내역이 쌓인 경우 — 최신순
const MULTIPLE: FeePayment[] = [
NEEDS_CHECK,
{
...PAID,
confirmedAt: "2026-03-05T13:30",
id: "2026-1",
paidAt: "2026-03-04T11:49",
},
];

// 상태를 바꿔 납부내역 화면을 확인한다
type FeeMockState = "checking" | "needs-check" | "paid" | "multiple";
const FEE_MOCK_STATE: FeeMockState = "multiple";

const FEE_PAYMENTS: Record<FeeMockState, FeePayment[]> = {
checking: [CHECKING],
multiple: MULTIPLE,
"needs-check": [NEEDS_CHECK],
paid: [PAID],
};

export const FEE_PAYMENTS_MOCK = FEE_PAYMENTS[FEE_MOCK_STATE];
18 changes: 18 additions & 0 deletions src/entities/fee/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// 납부확인중 · 확인필요 · 납부완료 — 미납이면 내역이 없다(홈에서 납부하기로 안내)
export type FeePaymentStatus = "checking" | "needs-check" | "paid";

interface FeePaymentBase {
id: string;
title: string;
// 확인 전이거나 입금자를 확인하지 못하면 금액·확인일시가 비어 있다
amount: number | null;
paidAt: string;
confirmedAt: string | null;
}

// 학생회 확인 내용은 확인필요일 때만 온다 — 상태로 갈라 두어 화면이 상태만 보고 판단하게 한다
export type FeePayment = FeePaymentBase &
(
| { status: "checking" | "paid" }
| { status: "needs-check"; reviewNote: string }
);
18 changes: 18 additions & 0 deletions src/entities/feedbacks/feedbacksApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { MY_FEEDBACKS_MOCK } from "@/entities/feedbacks/feedbacksMock";
import type { MyFeedback } from "@/entities/feedbacks/types";
import { mockupApi } from "@/lib/mockupApi";

// 실 API가 붙으면 함수 안쪽만 요청 코드로 바꾼다 — 화면은 이 함수로만 데이터를 받는다.
export function fetchMyFeedbacks(): Promise<MyFeedback[]> {
return mockupApi("feedbacks/mine", () => MY_FEEDBACKS_MOCK);
}

// 없는 피드백은 null — 작성내역 상세 화면이 작성내역 목록으로 보낸다.
export function fetchMyFeedback(
feedbackId: string,
): Promise<MyFeedback | null> {
return mockupApi(
`feedbacks/mine/${feedbackId}`,
() => MY_FEEDBACKS_MOCK.find((item) => item.id === feedbackId) ?? null,
);
}
Loading