diff --git a/src/api/adminMembers.ts b/src/api/adminMembers.ts index 4dce6ff..9a57f93 100644 --- a/src/api/adminMembers.ts +++ b/src/api/adminMembers.ts @@ -5,10 +5,49 @@ export type MemberStatus = 'ACTIVE' | 'DORMANT' | 'BANNED' export type MemberTier = 'FREE' | 'AD_FREE' | 'PREMIUM' export type MemberRole = 'USER' | 'ADMIN' +export interface DogDetail { + id: number + name: string + breed: string | null + size: string | null + gender: string | null + neutered: boolean + birthDate: string | null + imageUrl: string | null +} +export interface CheckInDetail { + spotName: string + region: string | null + checkedInAt: string +} +export interface MatchDetail { + id: number + direction: string + counterpartDog: string + status: string + createdAt: string +} +export interface SubscriptionDetail { + tier: string + platform: string + startedAt: string + expiresAt: string + active: boolean +} +export interface MemberDetail { + member: Member + dogs: DogDetail[] + checkIns: CheckInDetail[] + matches: MatchDetail[] + subscriptions: SubscriptionDetail[] +} + /** 관리자 회원관리 API — /api/admin/members (ADMIN 전용) */ export const adminMembersApi = { list: () => http.get('/api/admin/members'), + detail: (id: number) => http.get(`/api/admin/members/${id}`), + updateStatus: (id: number, status: MemberStatus) => http.put(`/api/admin/members/${id}/status`, { status }), diff --git a/src/api/adminStats.ts b/src/api/adminStats.ts new file mode 100644 index 0000000..da43b01 --- /dev/null +++ b/src/api/adminStats.ts @@ -0,0 +1,32 @@ +import { http } from './http' + +export interface Count { + key: string + count: number +} + +export interface StatsSummary { + members: { total: number; byTier: Count[]; byStatus: Count[] } + today: { signups: number; withdrawals: number } + visitors: { dau: number; wau: number; mau: number } +} + +export interface VisitorPoint { + label: string + count: number +} + +export interface RegionStat { + region: string + checkIns: number + users: number +} + +export type VisitorUnit = 'day' | 'week' | 'month' + +/** 관리자 대시보드 통계 API (체크인 기반) */ +export const adminStatsApi = { + summary: () => http.get('/api/admin/stats/summary'), + visitors: (unit: VisitorUnit) => http.get(`/api/admin/stats/visitors?unit=${unit}`), + regions: () => http.get('/api/admin/stats/regions'), +} diff --git a/src/layouts/AdminLayout.vue b/src/layouts/AdminLayout.vue index 89a1913..c30072e 100644 --- a/src/layouts/AdminLayout.vue +++ b/src/layouts/AdminLayout.vue @@ -58,7 +58,10 @@ const admin = useAdminAuthStore() const drawer = ref(true) -const menu = [{ name: 'admin-members', label: '회원 관리', icon: 'group' }] +const menu = [ + { name: 'admin-dashboard', label: '대시보드', icon: 'dashboard' }, + { name: 'admin-members', label: '회원 관리', icon: 'group' }, +] async function onLogout() { await admin.logout() diff --git a/src/pages/admin/AdminDashboardPage.vue b/src/pages/admin/AdminDashboardPage.vue new file mode 100644 index 0000000..10164fe --- /dev/null +++ b/src/pages/admin/AdminDashboardPage.vue @@ -0,0 +1,253 @@ + + + + + diff --git a/src/pages/admin/AdminLoginPage.vue b/src/pages/admin/AdminLoginPage.vue index a42f068..d19a833 100644 --- a/src/pages/admin/AdminLoginPage.vue +++ b/src/pages/admin/AdminLoginPage.vue @@ -126,7 +126,7 @@ async function finishLogin(login: () => Promise<{ role: string }>) { error.value = '관리자 권한이 없는 계정입니다.' return } - await router.replace({ name: 'admin-members' }) + await router.replace({ name: 'admin-dashboard' }) } catch (e) { error.value = e instanceof ApiError ? e.message : '로그인 중 오류가 발생했습니다.' } finally { diff --git a/src/pages/admin/AdminMemberDetailPage.vue b/src/pages/admin/AdminMemberDetailPage.vue new file mode 100644 index 0000000..04eff28 --- /dev/null +++ b/src/pages/admin/AdminMemberDetailPage.vue @@ -0,0 +1,203 @@ + + + diff --git a/src/pages/admin/AdminMembersPage.vue b/src/pages/admin/AdminMembersPage.vue index a451f3e..8c5ceda 100644 --- a/src/pages/admin/AdminMembersPage.vue +++ b/src/pages/admin/AdminMembersPage.vue @@ -107,9 +107,15 @@ - +