Files
bowlingManager/frontend/src/views/club/Dashboard.vue
T
2025-07-03 08:36:26 +09:00

335 lines
9.7 KiB
Vue

<template>
<div class="club-dashboard">
<h2>클럽 대시보드</h2>
<div v-if="loading" class="card flex justify-content-center">
<ProgressSpinner />
</div>
<div v-else class="grid">
<!-- 회원 통계 카드 -->
<div class="col-12 md:col-6 lg:col-6">
<div class="dashboard-card">
<div class="card-header">
<i class="pi pi-users"></i>
<h3>회원 현황</h3>
</div>
<div class="card-content">
<p> 회원: {{ memberStats.totalCount }}</p>
<p>정회원: {{ memberStats.regularCount }}</p>
<p>준회원: {{ memberStats.associateCount }}</p>
</div>
<div class="card-footer">
<Button label="회원 관리" icon="pi pi-arrow-right" @click="navigateTo('/club/members')" />
</div>
</div>
</div>
<!-- 점수 통계 카드 -->
<div class="col-12 md:col-6 lg:col-6">
<div class="dashboard-card">
<div class="card-header">
<i class="pi pi-chart-line"></i>
<h3>점수 통계</h3>
</div>
<div class="card-content">
<p>최고 게임: {{ scoreStats.topGame || 0 }}</p>
<p>평균 점수: {{ scoreStats.averageScore || 0 }}</p>
<p>전체 게임: {{ scoreStats.totalGames || 0 }}게임</p>
</div>
<div class="card-footer">
<Button label="통계 보기" icon="pi pi-arrow-right" @click="navigateTo('/club/statistics')" />
</div>
</div>
</div>
<!-- 다음 모임 카드(여러 ) -->
<div class="col-12 md:col-6 lg:col-6">
<div class="dashboard-card">
<div class="card-header">
<i class="pi pi-calendar"></i>
<h3>다음 모임</h3>
</div>
<div class="card-content" v-if="nextMeetings && nextMeetings.length">
<table class="recent-meetings-table" style="width:100%; border-collapse:collapse;">
<thead>
<tr>
<th>일시</th>
<th>제목</th>
<th>장소</th>
<th>참가자</th>
</tr>
</thead>
<tbody>
<tr v-for="meeting in nextMeetings.slice(0, 3)" :key="meeting.id || meeting.date">
<td>{{ formatDate(meeting.date) }}</td>
<td>{{ meeting.title || '-' }}</td>
<td>{{ meeting.location || '미정' }}</td>
<td>{{ meeting.participantsCount || meeting.participants || 0 }}</td>
</tr>
</tbody>
</table>
</div>
<div class="card-content" v-else>
<p>예정된 모임이 없습니다.</p>
</div>
<div class="card-footer">
<Button label="모임 관리" icon="pi pi-arrow-right" @click="navigateTo('/club/events')" />
</div>
</div>
</div>
<!-- 최근 모임 카드: 최근 3 리스트로 -->
<div class="col-12 md:col-6 lg:col-6">
<div class="dashboard-card">
<div class="card-header">
<i class="pi pi-chart-bar"></i>
<h3>최근 모임</h3>
</div>
<div class="card-content">
<table class="recent-meetings-table" style="width:100%; border-collapse:collapse;">
<thead>
<tr>
<th>일시</th>
<th>제목</th>
<th>참가자</th>
<th>게임 </th>
</tr>
</thead>
<tbody>
<tr v-for="meeting in recentMeetings.slice(0, 3)" :key="meeting.id || meeting.date">
<td>{{ formatDate(meeting.date) }}</td>
<td>{{ meeting.title }}</td>
<td>{{ meeting.participantsCount || meeting.participants || 0 }}</td>
<td>{{ meeting.games }}게임</td>
</tr>
<tr v-if="recentMeetings.length === 0">
<td colspan="4" style="text-align:center;">최근 모임 데이터가 없습니다.</td>
</tr>
</tbody>
</table>
</div>
<div class="card-footer">
<Button label="기록 보기" icon="pi pi-arrow-right" @click="navigateTo('/club/events')" />
</div>
</div>
</div>
</div>
<div class="grid mt-4">
<!-- 상위 회원 순위 -->
<div class="col-12">
<div class="dashboard-card">
<div class="card-header">
<h3>에버리지 순위</h3>
</div>
<DataTable :value="topMembers" :rows="5" stripedRows responsiveLayout="scroll">
<template #empty>
<tr>
<td colspan="5" style="text-align:center;">순위 데이터가 없습니다.</td>
</tr>
</template>
<Column header="순위" :body="(_, { rowIndex }) => rowIndex + 1" style="width: 10%"></Column>
<Column field="name" header="이름" style="width: 30%"></Column>
<Column field="average" header="에버리지" :body="averageTemplate" style="width: 20%"></Column>
<Column field="games" header="게임 수" style="width: 20%"></Column>
<Column field="handicap" header="핸디캡" style="width: 20%"></Column>
</DataTable>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import apiClient from '@/services/api';
import dayjs from 'dayjs';
import { useToast } from 'primevue/usetoast';
import { useGtag } from 'vue-gtag-next';
const router = useRouter();
const toast = useToast();
const gtag = useGtag();
// 데이터 상태 변수들
const loading = ref(false);
const clubId = ref(null);
// 회원 통계
const memberStats = ref({
totalCount: 0,
regularCount: 0,
associateCount: 0
});
// 다음 모임 정보 (여러 개)
const nextMeetings = ref([]);
// 최근 모임 정보
const lastMeeting = ref(null);
// 점수 통계
const scoreStats = ref({
topGame: 0,
averageScore: 0,
totalGames: 0
});
// 상위 회원 목록
const topMembers = ref([]);
// 최근 모임 목록
const recentMeetings = ref([]);
// 날짜 포맷 함수
const formatDate = (date) => {
return dayjs(date).format('YYYY-MM-DD HH:mm');
};
// 템플릿 함수들
const rankTemplate = (rowData, column) => {
return rowData.rank || topMembers.value.indexOf(rowData) + 1;
};
const averageTemplate = (rowData) => {
return rowData.average ? rowData.average.toFixed(1) : '0.0';
};
const dateTemplate = (rowData) => {
return formatDate(rowData.date);
};
// 데이터 로드 함수
const loadDashboardData = async () => {
if (!clubId.value) {
console.error('클럽 ID가 없습니다.');
return;
}
try {
loading.value = true;
// 회원 통계 로드
const membersResponse = await apiClient.post('/api/club/members/stats', {
clubId: clubId.value
});
memberStats.value = membersResponse.data;
// 다음 모임 정보 로드 (여러 개)
const nextMeetingResponse = await apiClient.post('/api/club/meetings/next', {
clubId: clubId.value
});
const data = nextMeetingResponse.data;
nextMeetings.value = Array.isArray(data) ? data : (data ? [data] : []);
// 최근 모임 정보 로드
const lastMeetingResponse = await apiClient.post('/api/club/meetings/last', {
clubId: clubId.value
});
lastMeeting.value = lastMeetingResponse.data;
// 점수 통계 로드
const scoreStatsResponse = await apiClient.post('/api/club/scores/stats', {
clubId: clubId.value
});
scoreStats.value = scoreStatsResponse.data;
// 상위 회원 목록 로드
const topMembersResponse = await apiClient.post('/api/club/members/top', {
clubId: clubId.value
});
topMembers.value = topMembersResponse.data;
// 최근 모임 목록 로드
const recentMeetingsResponse = await apiClient.post('/api/club/meetings/recent', {
clubId: clubId.value
});
recentMeetings.value = recentMeetingsResponse.data;
} catch (error) {
console.error('대시보드 데이터 로딩 오류:', error);
toast.add({
severity: 'error',
summary: '오류',
detail: '대시보드 데이터를 불러오는 중 오류가 발생했습니다.',
life: 3000
});
} finally {
loading.value = false;
}
};
// 페이지 이동 함수
const navigateTo = (path) => {
// Google Analytics 이벤트 추적
gtag.event('navigation', {
'event_category': '대시보드',
'event_label': path,
'value': 1
});
router.push(path);
};
// 컴포넌트 마운트 시 데이터 로드
onMounted(async () => {
clubId.value = localStorage.getItem('clubId') || null;
await loadDashboardData();
// Google Analytics 페이지뷰 추적
gtag.pageview({
page_title: '클럽 대시보드',
page_path: window.location.pathname,
page_location: window.location.href
});
});
</script>
<style scoped>
.club-dashboard {
padding: 1rem;
}
.dashboard-card {
background: #ffffff;
border-radius: 10px;
padding: 1rem;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
height: 100%;
display: flex;
flex-direction: column;
}
.card-header {
display: flex;
align-items: center;
margin-bottom: 1rem;
gap: 0.5rem;
}
.card-header i {
font-size: 1.5rem;
color: var(--primary-color);
}
.card-header h3 {
margin: 0;
color: var(--text-color);
font-size: 1.2rem;
}
.card-content {
flex: 1;
margin-bottom: 1rem;
}
.card-content p {
margin: 0.5rem 0;
color: var(--text-color-secondary);
}
.card-footer {
margin-top: auto;
}
</style>