feat(point): 포인트 reason 한글화 + 월별 조회 네비게이션
Deploy / deploy (push) Failing after 12m8s

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
ByungCheol
2026-07-09 23:47:09 +09:00
parent cdaa7d09ac
commit 6f49a7eef0
2 changed files with 80 additions and 8 deletions
+4 -3
View File
@@ -47,9 +47,10 @@ export const authApi = {
points() { points() {
return http.get('/auth/points') return http.get('/auth/points')
}, },
// 포인트 적립/차감 내역 (최신순) // 포인트 적립/차감 내역 — year+month 지정 시 해당 월, 미지정 시 최근 100건
pointHistory() { pointHistory(year, month) {
return http.get('/auth/point-history') const params = year && month ? { year, month } : {}
return http.get('/auth/point-history', { params })
}, },
// 현재 사용자 전체 프로필(아바타·포인트 포함) — 재로그인 없이 최신값 동기화 // 현재 사용자 전체 프로필(아바타·포인트 포함) — 재로그인 없이 최신값 동기화
profile() { profile() {
+76 -5
View File
@@ -26,24 +26,59 @@ onMounted(async () => {
}) })
// 포인트 적립내역 모달 // 포인트 적립내역 모달
const POINT_REASONS = { BOARD_WRITE: '게시판 작성' } const POINT_REASONS = {
BOARD_WRITE: '게시판 작성',
POST_UPVOTED: '게시글 추천받음',
POST_DOWNVOTED: '게시글 비추천받음',
VOTE_REWARD: '추천 참여',
VOTE_CANCEL: '추천 취소',
ACCOUNT_ENTRY: '가계부 기록',
}
function reasonLabel(r) { function reasonLabel(r) {
if (r && r.startsWith('MONTH_COMPLETE_')) {
const ym = r.slice('MONTH_COMPLETE_'.length)
return `${ym.slice(0, 4)}${parseInt(ym.slice(4), 10)}월 개근 보너스`
}
return POINT_REASONS[r] || r return POINT_REASONS[r] || r
} }
const _now = new Date()
const histYear = ref(_now.getFullYear())
const histMonth = ref(_now.getMonth() + 1)
const historyOpen = ref(false) const historyOpen = ref(false)
const history = ref([]) const history = ref([])
const historyLoading = ref(false) const historyLoading = ref(false)
async function openHistory() {
historyOpen.value = true const isCurrentMonth = computed(() => {
const n = new Date()
return histYear.value === n.getFullYear() && histMonth.value === n.getMonth() + 1
})
async function loadHistory() {
historyLoading.value = true historyLoading.value = true
try { try {
history.value = await authApi.pointHistory() history.value = await authApi.pointHistory(histYear.value, histMonth.value)
} catch { } catch {
history.value = [] history.value = []
} finally { } finally {
historyLoading.value = false historyLoading.value = false
} }
} }
function prevMonth() {
if (histMonth.value === 1) { histYear.value--; histMonth.value = 12 }
else histMonth.value--
loadHistory()
}
function nextMonth() {
if (isCurrentMonth.value) return
if (histMonth.value === 12) { histYear.value++; histMonth.value = 1 }
else histMonth.value++
loadHistory()
}
async function openHistory() {
historyOpen.value = true
await loadHistory()
}
function fmtDateTime(s) { function fmtDateTime(s) {
if (!s) return '' if (!s) return ''
const d = new Date(s) const d = new Date(s)
@@ -376,8 +411,13 @@ async function withdraw() {
<strong>포인트 적립내역</strong> <strong>포인트 적립내역</strong>
<button type="button" class="ph-close" aria-label="닫기" @click="historyOpen = false">×</button> <button type="button" class="ph-close" aria-label="닫기" @click="historyOpen = false">×</button>
</div> </div>
<div class="ph-month-nav">
<button type="button" class="ph-nav-btn" @click="prevMonth"></button>
<span class="ph-month-label">{{ histYear }} {{ histMonth }}</span>
<button type="button" class="ph-nav-btn" :disabled="isCurrentMonth" @click="nextMonth"></button>
</div>
<p v-if="historyLoading" class="ph-msg">불러오는 </p> <p v-if="historyLoading" class="ph-msg">불러오는 </p>
<p v-else-if="!history.length" class="ph-msg">적립 내역이 없습니다.</p> <p v-else-if="!history.length" class="ph-msg"> 적립 내역이 없습니다.</p>
<ul v-else class="ph-list"> <ul v-else class="ph-list">
<li v-for="(h, i) in history" :key="i"> <li v-for="(h, i) in history" :key="i">
<div class="ph-main"> <div class="ph-main">
@@ -655,6 +695,37 @@ async function withdraw() {
line-height: 1; line-height: 1;
cursor: pointer; cursor: pointer;
} }
.ph-month-nav {
display: flex;
align-items: center;
justify-content: center;
gap: 0.75rem;
padding: 0.55rem 1rem;
border-bottom: 1px solid var(--color-border);
}
.ph-month-label {
font-size: 0.9rem;
font-weight: 600;
min-width: 7rem;
text-align: center;
}
.ph-nav-btn {
border: 0;
background: transparent;
color: var(--color-text);
font-size: 1.3rem;
line-height: 1;
cursor: pointer;
padding: 0.1rem 0.4rem;
border-radius: 4px;
}
.ph-nav-btn:disabled {
opacity: 0.25;
cursor: default;
}
.ph-nav-btn:not(:disabled):hover {
background: var(--color-background-mute);
}
.ph-msg { .ph-msg {
padding: 2rem 1rem; padding: 2rem 1rem;
text-align: center; text-align: center;