diff --git a/docs/release-2026-06-06.md b/docs/release-2026-06-06.md index 8f141cb..26c1766 100644 --- a/docs/release-2026-06-06.md +++ b/docs/release-2026-06-06.md @@ -5,7 +5,7 @@ --- ## 1. 홈 대시보드 -- **일별 수입·지출 캘린더** 추가 (예산 대비 지출 아래). 날짜에 마우스 오버/탭 시 그 날의 내역 목록 표시. +- **일별 수입·지출 캘린더** 추가 (예산 대비 지출 아래). 날짜를 **탭하면 그 날의 내역을 팝업(모달)으로** 표시(시인성 개선·단일 탭 동작). ## 2. 투자 / 계좌 - **시세 자동조회**: 종목코드(6자리)로 네이버 금융에서 현재가 자동 갱신. 투자 탭 진입·포트폴리오 펼침 시 자동, "↻ 시세 갱신" 버튼도 제공. @@ -39,8 +39,41 @@ --- +# (이어진 작업) 앱 UX · 보안 · 품질 강화 + +## 8. 앱 하단 내비게이션 +- 화면 **하단 고정 아이콘 바**(뒤로 · 앞으로 · 홈 · 새로고침 · 설정). 스크롤과 무관하게 항상 노출, **안드로이드 소프트키 위** 안전영역에 배치. +- 사이드바의 '홈' 메뉴 제거(하단 내비로 대체). 로그인 전에는 햄버거 메뉴·네이버 로그인 버튼 숨김. + +## 9. 설정 / 계정정보 +- **설정 화면**(하단 내비 → 설정): 계정정보 · 앱 버전(`v1.0.0`) · **App Data 삭제**(로그인/캐시 초기화 후 재시작). +- **계정정보 상세 → 정보변경**: 비밀번호 재인증 후 **이름 · 이메일 · 비밀번호를 한 화면에서** 변경. 헤더의 비밀번호 변경 아이콘·모달은 제거(진입점 일원화). +- **로그인 후 대시보드(`/`)로 이동** 통일. + +## 10. 투자 매매내역 +- 종목별 매매이력을 **인라인 최근 5건 + "전체 N건 보기" 스크롤 모달**로 정리(길어짐 방지). +- **매매 수정 기능** 추가(`PUT /api/account/invest/trades/{id}`). 수정 시 시점별 시뮬레이션으로 **보유수량 음수 방지** 검증. + +## 11. 보안 / 인증 (백엔드) +- **세션 DB 이중 저장**(`auth_session`): Redis 유실·재시작에도 로그인 유지(Redis 미스 시 DB에서 복원·재수화). +- **무차별 대입 방지**: 실패한 시도만 카운트 — 로그인 IP당 10회/10분, 비밀번호 재인증 회원당 5회/10분 초과 시 429. +- **인터셉터 보호 경로 누락 수정**: `verify-password`·`profile`이 인증 없이 500 나던 문제 → 401 정상화. +- **전역 예외 처리 강화**: 미처리 500 로그에 메서드+경로 기록(추적성), 잘못된 JSON은 400. + +## 12. 품질 / 배포 파이프라인 +- **자동화 테스트 도입**: 프론트 **Vitest 32**(스토어·API·로그인 플로우), 백엔드 **JUnit 34**(단위 + `@WebMvcTest` 통합). +- **테스트 게이트**: CI와 **배포 워크플로 모두 테스트 실패 시 배포 중단**. 백엔드 배포에 CI와 동일한 MariaDB/Redis 서비스 추가. +- 번들: 마크다운 에디터는 라우트 지연 청크임을 명시(경고 한도 현실화). + +## 13. 안드로이드 +- 에지투에지(`targetSdk 36`) 대응: **콘텐츠 루트에 시스템 바 인셋 패딩** 적용 → 헤더는 상태바 아래, 하단 내비는 소프트키 위. + +--- + ## 운영 반영 시 체크 - 서버 `/opt/sb-backend/.env` - `ACCOUNT_CRYPTO_KEY` — 계좌번호 암호화를 켤 때만(미설정 시 평문, 깨지지 않음). -- `app_setting` 테이블 — 배포 시 `member.sql` 초기화로 자동 생성(회원가입 허용 플래그). -- Redis `appendonly yes` — 세션 영속(이미 적용). +- `app_setting` · `auth_session` 테이블 — 배포 시 `member.sql` 초기화로 자동 생성(가입 허용 플래그 / 세션 DB 백업). +- Redis `appendonly yes` — 세션 영속(이미 적용). 단, 세션 DB 이중 저장으로 Redis 유실에도 로그인 유지. +- 앱 버전 표기는 `package.json` 의 `version`(현재 `1.0.0`)을 단일 출처로 사용. +- 배포는 `main` 푸시 → CI·Deploy 워크플로 모두 **테스트 통과해야 진행**(Gitea Actions). diff --git a/src/views/HomeView.vue b/src/views/HomeView.vue index fe3daa5..0239102 100644 --- a/src/views/HomeView.vue +++ b/src/views/HomeView.vue @@ -66,8 +66,16 @@ const calendarCells = computed(() => { return cells }) -// 마우스 오버 / 탭한 날짜의 내역 목록 +// 탭한 날짜의 내역을 팝업(모달)으로 표시 — 캘린더 아래가 가려져도 잘 보이게 const activeDay = ref(0) +const dayModalOpen = ref(false) +function openDay(c) { + activeDay.value = c.day + dayModalOpen.value = true +} +function closeDayModal() { + dayModalOpen.value = false +} const activeItems = computed(() => (activeDay.value ? dayMap.value[activeDay.value]?.items || [] : [])) const activeDateLabel = computed(() => { if (!activeDay.value) return '' @@ -230,8 +238,7 @@ onMounted(load) type="button" class="cal-cell" :class="{ today: c.day === todayDate, active: c.day === activeDay, has: c.items.length }" - @mouseenter="activeDay = c.day" - @click="activeDay = activeDay === c.day ? 0 : c.day" + @click="openDay(c)" > {{ c.day }} +{{ won(c.income) }} @@ -240,24 +247,35 @@ onMounted(load) - -
- -

날짜에 마우스를 올리거나 탭하면 내역이 표시됩니다.

-
+

날짜를 탭하면 그 날의 내역이 표시됩니다.

+ + + +
+ +
+
+
+
@@ -490,57 +508,117 @@ onMounted(load) .cal-amt.expense { color: #c0392b; } -.cal-detail { - margin-top: 0.7rem; - padding-top: 0.7rem; - border-top: 1px solid var(--color-border); - min-height: 1.2rem; +.cal-hint { + margin: 0.6rem 0 0; + font-size: 0.76rem; + opacity: 0.5; + text-align: center; } -.cd-date { - font-size: 0.82rem; + +/* 일별 내역 팝업 */ +.day-modal-backdrop { + position: fixed; + inset: 0; + z-index: 1100; + display: flex; + align-items: center; + justify-content: center; + background: rgba(0, 0, 0, 0.5); + padding: 1rem; +} +.day-modal { + position: relative; + width: 100%; + max-width: 360px; + max-height: 75vh; + overflow-y: auto; + padding: 1.4rem 1.25rem 1.25rem; + background: var(--color-background); + border: 1px solid var(--color-border); + border-radius: 10px; + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.3); +} +.day-modal-close { + position: absolute; + top: 0.4rem; + right: 0.55rem; + width: 2rem; + height: 2rem; + border: 0; + background: transparent; + color: var(--color-text); + font-size: 1.5rem; + line-height: 1; + cursor: pointer; +} +.dm-date { + font-size: 1rem; font-weight: 700; margin-bottom: 0.4rem; } -.cd-list { +.dm-sum { + display: flex; + gap: 0.8rem; + font-size: 0.82rem; + font-weight: 600; + margin-bottom: 0.7rem; + padding-bottom: 0.6rem; + border-bottom: 1px solid var(--color-border); +} +.dm-sum .income { + color: #2e7d32; +} +.dm-sum .expense { + color: #c0392b; +} +.dm-list { list-style: none; padding: 0; margin: 0; display: flex; flex-direction: column; - gap: 0.25rem; + gap: 0.5rem; } -.cd-item { +.dm-item { display: flex; align-items: baseline; gap: 0.5rem; - font-size: 0.82rem; + font-size: 0.86rem; } -.cd-cat { +.dm-cat { font-weight: 600; flex-shrink: 0; } -.cd-memo { +.dm-memo { opacity: 0.6; - font-size: 0.76rem; + font-size: 0.78rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } -.cd-amt { +.dm-amt { margin-left: auto; font-weight: 700; white-space: nowrap; } -.cd-amt.income { +.dm-amt.income { color: #2e7d32; } -.cd-amt.expense { +.dm-amt.expense { color: #c0392b; } -.cd-hint { - font-size: 0.78rem; - opacity: 0.5; - margin: 0; +.dm-empty { + font-size: 0.86rem; + opacity: 0.6; + margin: 0.5rem 0 0; +} +.fade-enter-active, +.fade-leave-active { + transition: opacity 0.18s ease; +} +.fade-enter-from, +.fade-leave-to { + opacity: 0; } /* 바로가기 그리드 */