gtag 추가
This commit is contained in:
Generated
+26
@@ -33,6 +33,8 @@
|
||||
"vee-validate": "^4.12.5",
|
||||
"vue": "^3.5.13",
|
||||
"vue-chartjs": "^5.3.2",
|
||||
"vue-gtag": "^3.5.1",
|
||||
"vue-gtag-next": "^1.14.0",
|
||||
"vue-router": "^4.5.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"xlsx": "^0.18.5",
|
||||
@@ -3748,6 +3750,30 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-gtag": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-gtag/-/vue-gtag-3.5.1.tgz",
|
||||
"integrity": "sha512-fyXQeIIc2cMibgggCbIZKfP0NvVbMupi4yl7CIwnD2feL9MGVVjzN/3nb5mX9AmZhIrkYri9dmBX6jY0lRCKDg==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vue": "^3.5.13",
|
||||
"vue-router": "^4.5.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"vue-router": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/vue-gtag-next": {
|
||||
"version": "1.14.0",
|
||||
"resolved": "https://registry.npmjs.org/vue-gtag-next/-/vue-gtag-next-1.14.0.tgz",
|
||||
"integrity": "sha512-iJl+cOG2GU5NuxqzSSIpt03WVOvZqyKB9TOy7d55KiuvRklcnb2nlqxW5B/a3/sbIt7fla+XEkRyMCcoz0zAHw==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"vue": "^3.0.0-rc.11"
|
||||
}
|
||||
},
|
||||
"node_modules/vue-router": {
|
||||
"version": "4.5.1",
|
||||
"resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.5.1.tgz",
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
"vee-validate": "^4.12.5",
|
||||
"vue": "^3.5.13",
|
||||
"vue-chartjs": "^5.3.2",
|
||||
"vue-gtag": "^3.5.1",
|
||||
"vue-gtag-next": "^1.14.0",
|
||||
"vue-router": "^4.5.0",
|
||||
"vuedraggable": "^4.1.0",
|
||||
"xlsx": "^0.18.5",
|
||||
|
||||
@@ -191,8 +191,29 @@
|
||||
}
|
||||
|
||||
</style>
|
||||
<!-- Google Tag Manager -->
|
||||
<script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
|
||||
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
|
||||
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
|
||||
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
|
||||
})(window,document,'script','dataLayer','GTM-PPDRKT7Q');</script>
|
||||
<!-- End Google Tag Manager -->
|
||||
<script>
|
||||
// 페이지 진입 시 GA 페이지뷰 이벤트 전송
|
||||
window.dataLayer = window.dataLayer || [];
|
||||
window.dataLayer.push({
|
||||
event: 'page_view',
|
||||
page_title: document.title,
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Google Tag Manager (noscript) -->
|
||||
<noscript><iframe src="https://www.googletagmanager.com/ns.html?id=GTM-PPDRKT7Q"
|
||||
height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
|
||||
<!-- End Google Tag Manager (noscript) -->
|
||||
<div class="container">
|
||||
<h1>볼링공 속도 계산기</h1>
|
||||
|
||||
@@ -206,8 +227,14 @@
|
||||
<p>볼링공이 파울라인을 지날 때 '시작'을 누르고, 헤드핀에 도달할 때 '정지'를 누르세요.</p>
|
||||
<div class="stopwatch-display" id="stopwatch-display">00:00.000</div>
|
||||
<div class="stopwatch-container">
|
||||
<button class="toggle-btn" id="toggle-btn">시작</button>
|
||||
<button class="reset-btn" id="reset-btn">초기화</button>
|
||||
<button class="toggle-btn" id="toggle-btn"
|
||||
onclick="dataLayer.push({event:'button_click', event_category:'볼스피드', event_label:'스톱워치'});">
|
||||
시작
|
||||
</button> <!-- [GA] 시작 버튼 클릭 추적 -->
|
||||
<button class="reset-btn" id="reset-btn"
|
||||
onclick="dataLayer.push({event:'button_click', event_category:'볼스피드', event_label:'초기화'});">
|
||||
초기화
|
||||
</button> <!-- [GA] 초기화 버튼 클릭 추적 -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -229,8 +256,8 @@
|
||||
</div>
|
||||
|
||||
<div class="buttons">
|
||||
<button onclick="calculateSpeed()">계산하기</button>
|
||||
<button onclick="resetValues()">초기화</button>
|
||||
<button onclick="dataLayer.push({event:'button_click', event_category:'볼스피드', event_label:'계산하기'}); calculateSpeed()">계산하기</button> <!-- [GA] 계산하기 버튼 클릭 추적 -->
|
||||
<button onclick="dataLayer.push({event:'button_click', event_category:'볼스피드', event_label:'계산초기화'}); resetValues()">초기화</button> <!-- [GA] 계산초기화 버튼 클릭 추적 -->
|
||||
</div>
|
||||
|
||||
<div class="results" id="results">
|
||||
|
||||
@@ -7,7 +7,27 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
// 별도 로직 없음
|
||||
import { useGtag } from 'vue-gtag-next'
|
||||
|
||||
// Google Analytics(GA) 추적용 gtag 인스턴스 사용
|
||||
const gtag = useGtag()
|
||||
|
||||
// 컴포넌트 마운트 시 페이지뷰 이벤트 발생
|
||||
// (퍼블릭 레이아웃 진입 시 추적)
|
||||
gtag.pageview({
|
||||
page_title: '퍼블릭 레이아웃',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
})
|
||||
|
||||
// 주요 버튼 클릭 등 액션 추적용 함수 예시
|
||||
const trackPublicAction = (actionName) => {
|
||||
gtag.event('button_click', {
|
||||
event_category: '퍼블릭',
|
||||
event_label: actionName,
|
||||
value: 1
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -5,8 +5,33 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { useGtag } from 'vue-gtag-next'
|
||||
|
||||
export default {
|
||||
name: 'ClubLayout'
|
||||
name: 'ClubLayout',
|
||||
setup() {
|
||||
const gtag = useGtag()
|
||||
|
||||
// 컴포넌트 마운트 시 페이지뷰 이벤트 발생
|
||||
gtag.pageview({
|
||||
page_title: '클럽 레이아웃',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
})
|
||||
|
||||
// 이벤트 추적 메서드 예시
|
||||
const trackButtonClick = (buttonName) => {
|
||||
gtag.event('button_click', {
|
||||
'event_category': '클럽',
|
||||
'event_label': buttonName,
|
||||
'value': 1
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
trackButtonClick
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import App from './App.vue'
|
||||
import router from './router'
|
||||
import './assets/base.css'
|
||||
|
||||
// Google Tag Manager 설정
|
||||
import VueGtag from 'vue-gtag-next'
|
||||
|
||||
// dayjs configuration
|
||||
import dayjs from 'dayjs'
|
||||
import timezone from 'dayjs/plugin/timezone'
|
||||
@@ -79,6 +82,16 @@ app.use(ToastService)
|
||||
app.use(createPinia())
|
||||
app.use(router)
|
||||
|
||||
// Google Analytics(GA) vue-gtag-next 플러그인 등록
|
||||
app.use(VueGtag, {
|
||||
property: {
|
||||
id: 'GTM-PPDRKT7Q'
|
||||
},
|
||||
appName: '볼링매니저',
|
||||
pageTrackerScreenviewEnabled: true
|
||||
}, router)
|
||||
// GTM-PPDRKT7Q: 구글 태그매니저(GA4) ID
|
||||
|
||||
// Register custom components
|
||||
app.component('AppHeader', AppHeader);
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import adminService from '@/services/adminService';
|
||||
|
||||
@@ -58,7 +59,14 @@ const currentPage = ref(1);
|
||||
const rowsPerPage = ref(20);
|
||||
|
||||
// 페이지 로드 시 활동 로그 목록 가져오기
|
||||
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||
onMounted(async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.pageview({
|
||||
page_title: '활동로그관리',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
await fetchLogs();
|
||||
});
|
||||
|
||||
|
||||
@@ -248,8 +248,10 @@ import { ref, reactive, onMounted } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import dayjs from 'dayjs';
|
||||
import adminService from '@/services/adminService';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
|
||||
const toast = useToast();
|
||||
const gtag = useGtag();
|
||||
|
||||
// 상태 변수
|
||||
const loading = ref(false);
|
||||
@@ -319,7 +321,14 @@ const member = reactive({
|
||||
const canChangeOwner = ref(true);
|
||||
|
||||
// 페이지 로드 시 클럽 목록 가져오기
|
||||
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||
onMounted(async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.pageview({
|
||||
page_title: '클럽관리',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
await fetchClubs();
|
||||
});
|
||||
|
||||
|
||||
@@ -129,6 +129,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import dayjs from 'dayjs';
|
||||
@@ -206,7 +207,14 @@ const chartOptions = {
|
||||
};
|
||||
|
||||
// 컴포넌트 마운트 시 데이터 로드
|
||||
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||
onMounted(async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.pageview({
|
||||
page_title: '관리자대시보드',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
try {
|
||||
loading.value = true;
|
||||
await loadDashboardData();
|
||||
|
||||
@@ -202,12 +202,14 @@
|
||||
|
||||
<script setup>
|
||||
import { getStatusLabel, getStatusSeverity, getSubscriptionStatusLabel, getSubscriptionStatusSeverity, SUBSCRIPTION_STATUS_OPTIONS, SUBSCRIPTION_STATUS_LABELS } from '@/utils/enumMappings';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { ref, onMounted, computed, watch } from 'vue';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import axios from 'axios';
|
||||
import adminService from '@/services/adminService';
|
||||
|
||||
const toast = useToast();
|
||||
const gtag = useGtag();
|
||||
const API_URL = 'http://localhost:3030/api';
|
||||
|
||||
// 데이터 정의
|
||||
@@ -272,7 +274,14 @@ const fetchPlanSubscriptions = async (planId) => {
|
||||
};
|
||||
|
||||
// 컴포넌트 마운트 시 데이터 로드
|
||||
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||
onMounted(async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.pageview({
|
||||
page_title: '구독플랜관리',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
await fetchPlans();
|
||||
await fetchFeatures();
|
||||
await fetchClubs();
|
||||
|
||||
@@ -125,6 +125,7 @@
|
||||
<script setup>
|
||||
import { getStatusLabel, getRoleName, getRoleSeverity, getStatusSeverity, STATUS_OPTIONS, ROLE_OPTIONS } from '@/utils/enumMappings';
|
||||
import { ref, onMounted, reactive, computed } from 'vue';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import adminService from '@/services/adminService';
|
||||
|
||||
@@ -153,7 +154,14 @@ const dialogTitle = computed(() => {
|
||||
});
|
||||
|
||||
// 페이지 로드 시 사용자 목록 가져오기
|
||||
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||
onMounted(async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.pageview({
|
||||
page_title: '사용자관리',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
await fetchUsers();
|
||||
});
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
</div>
|
||||
<Button type="submit" label="로그인" class="w-full" :loading="loading" />
|
||||
<div class="register-link">
|
||||
<p>계정이 없으신가요? <a href="#" @click.prevent="showRegisterForm = true">회원가입</a></p>
|
||||
<p>계정이 없으신가요? <a href="#" @click.prevent="showRegisterForm = true; gtag.event('show_register_form', { 'event_category': '인증', 'event_label': '회원가입 폼 표시' });">회원가입</a></p>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
|
||||
<Button type="submit" label="회원가입" class="w-full" :loading="loading" />
|
||||
<div class="login-link">
|
||||
<p>이미 계정이 있으신가요? <a href="#" @click.prevent="showRegisterForm = false">로그인</a></p>
|
||||
<p>이미 계정이 있으신가요? <a href="#" @click.prevent="showRegisterForm = false; gtag.event('show_login_form', { 'event_category': '인증', 'event_label': '로그인 폼 표시' });">로그인</a></p>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -74,14 +74,16 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, computed, inject } from 'vue';
|
||||
import { ref, computed, inject, onMounted } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import authService from '@/services/authService';
|
||||
|
||||
const user = inject('user');
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
const gtag = useGtag();
|
||||
|
||||
const username = ref('');
|
||||
const password = ref('');
|
||||
@@ -177,6 +179,13 @@ const validateRegisterForm = () => {
|
||||
const handleLogin = async () => {
|
||||
if (!validateForm()) return;
|
||||
|
||||
// Google Analytics 이벤트 추적 - 로그인 시도
|
||||
gtag.event('login_attempt', {
|
||||
'event_category': '인증',
|
||||
'event_label': '로그인 시도',
|
||||
'value': 1
|
||||
});
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
@@ -198,6 +207,12 @@ const handleLogin = async () => {
|
||||
// 로그인 성공 메시지
|
||||
toast.add({ severity: 'success', summary: '성공', detail: '로그인에 성공했습니다.', life: 3000 });
|
||||
|
||||
// Google Analytics 이벤트 추적 - 로그인 성공
|
||||
gtag.event('login', {
|
||||
'method': 'id_password',
|
||||
'user_role': userData.role
|
||||
});
|
||||
|
||||
// 사용자 정보 및 메뉴 아이템 로드 이벤트 발생
|
||||
window.dispatchEvent(new CustomEvent('user-logged-in'));
|
||||
|
||||
@@ -210,6 +225,13 @@ const handleLogin = async () => {
|
||||
} catch (error) {
|
||||
console.error('로그인 오류:', error);
|
||||
|
||||
// Google Analytics 이벤트 추적 - 로그인 실패
|
||||
gtag.event('login_failed', {
|
||||
'event_category': '인증',
|
||||
'event_label': error.response?.data?.message || '인증 실패',
|
||||
'value': 1
|
||||
});
|
||||
|
||||
// 오류 메시지 처리
|
||||
const errorMessage = error.response?.data?.message || '로그인에 실패했습니다. 이메일과 비밀번호를 확인해주세요.';
|
||||
toast.add({ severity: 'error', summary: '오류', detail: errorMessage, life: 3000 });
|
||||
@@ -222,6 +244,13 @@ const handleLogin = async () => {
|
||||
const handleRegister = async () => {
|
||||
if (!validateRegisterForm()) return;
|
||||
|
||||
// Google Analytics 이벤트 추적 - 회원가입 시도
|
||||
gtag.event('sign_up_attempt', {
|
||||
'event_category': '인증',
|
||||
'event_label': '회원가입 시도',
|
||||
'value': 1
|
||||
});
|
||||
|
||||
loading.value = true;
|
||||
|
||||
try {
|
||||
@@ -235,6 +264,11 @@ const handleRegister = async () => {
|
||||
// 회원가입 성공 메시지
|
||||
toast.add({ severity: 'success', summary: '성공', detail: '회원가입에 성공했습니다. 로그인해주세요.', life: 3000 });
|
||||
|
||||
// Google Analytics 이벤트 추적 - 회원가입 성공
|
||||
gtag.event('sign_up', {
|
||||
'method': 'form'
|
||||
});
|
||||
|
||||
// 로그인 폼으로 전환
|
||||
showRegisterForm.value = false;
|
||||
|
||||
@@ -251,6 +285,13 @@ const handleRegister = async () => {
|
||||
} catch (error) {
|
||||
console.error('회원가입 오류:', error);
|
||||
|
||||
// Google Analytics 이벤트 추적 - 회원가입 실패
|
||||
gtag.event('sign_up_failed', {
|
||||
'event_category': '인증',
|
||||
'event_label': error.response?.data?.message || '회원가입 실패',
|
||||
'value': 1
|
||||
});
|
||||
|
||||
// 오류 메시지 처리
|
||||
const errorMessage = error.response?.data?.message || '회원가입에 실패했습니다. 다시 시도해주세요.';
|
||||
toast.add({ severity: 'error', summary: '오류', detail: errorMessage, life: 3000 });
|
||||
|
||||
@@ -83,6 +83,7 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useVuelidate } from '@vuelidate/core';
|
||||
import { required, email, minLength, sameAs, helpers } from '@vuelidate/validators';
|
||||
@@ -134,7 +135,14 @@ const rules = computed(() => ({
|
||||
const v$ = useVuelidate(rules, form);
|
||||
|
||||
// 페이지 로드 시 사용자 정보 가져오기
|
||||
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||
onMounted(async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.pageview({
|
||||
page_title: '프로필',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
await fetchUserProfile();
|
||||
});
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@
|
||||
<script setup>
|
||||
import { AVERAGE_CALCULATION_PERIOD_OPTIONS } from '@/utils/enumMappings';
|
||||
import { ref, computed, onMounted, inject } from 'vue';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import clubService from '@/services/clubService';
|
||||
@@ -55,7 +56,14 @@ const loading = ref(false);
|
||||
const clubNameError = ref('');
|
||||
const clubDescriptionError = ref('');
|
||||
|
||||
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||
onMounted(() => {
|
||||
const gtag = useGtag();
|
||||
gtag.pageview({
|
||||
page_title: '클럽생성',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
authStore.loadUserInfo();
|
||||
});
|
||||
|
||||
@@ -81,7 +89,13 @@ const validateForm = () => {
|
||||
return isValid;
|
||||
};
|
||||
|
||||
// 클럽 생성 시도/성공/실패 GA 이벤트 추적
|
||||
const handleCreateClub = async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.event('create_club_attempt', {
|
||||
event_category: '클럽생성',
|
||||
event_label: '클럽 생성 시도'
|
||||
});
|
||||
if (!validateForm()) return;
|
||||
|
||||
if (!authStore.user) {
|
||||
|
||||
@@ -130,6 +130,7 @@
|
||||
import { AVERAGE_CALCULATION_PERIOD_OPTIONS, getMemberTypeLabel } from '@/utils/enumMappings';
|
||||
|
||||
import { ref, onMounted, computed, inject } from 'vue';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useClubStore } from '@/stores/clubStore';
|
||||
@@ -309,7 +310,13 @@ const getFeatureIcon = (featureType) => {
|
||||
};
|
||||
|
||||
// 클럽 정보 저장
|
||||
// 클럽 정보 저장 버튼 클릭 시 GA 이벤트 추적
|
||||
const saveClubInfo = async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.event('save_club_info', {
|
||||
event_category: '클럽설정',
|
||||
event_label: '클럽 정보 저장'
|
||||
});
|
||||
try {
|
||||
if (!clubId) {
|
||||
router.push('/club');
|
||||
|
||||
@@ -146,9 +146,11 @@ 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);
|
||||
@@ -260,6 +262,12 @@ const loadDashboardData = async () => {
|
||||
|
||||
// 페이지 이동 함수
|
||||
const navigateTo = (path) => {
|
||||
// Google Analytics 이벤트 추적
|
||||
gtag.event('navigation', {
|
||||
'event_category': '대시보드',
|
||||
'event_label': path,
|
||||
'value': 1
|
||||
});
|
||||
router.push(path);
|
||||
};
|
||||
|
||||
@@ -267,6 +275,13 @@ const navigateTo = (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>
|
||||
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
<script setup>
|
||||
// getEventTypeLabel 중복 import 제거. eventUtils.js의 getEventTypeLabel만 사용.
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import FullCalendar from '@fullcalendar/vue3';
|
||||
@@ -56,7 +57,14 @@ const loading = ref(false);
|
||||
const clubId = ref(localStorage.getItem('clubId') || null);
|
||||
|
||||
// 페이지 로드 시 데이터 가져오기
|
||||
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||
onMounted(async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.pageview({
|
||||
page_title: '이벤트캘린더',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
await fetchEvents();
|
||||
});
|
||||
|
||||
@@ -107,13 +115,27 @@ const getEventColor = (eventType) => {
|
||||
};
|
||||
|
||||
// 캘린더 이벤트 클릭 핸들러
|
||||
// 캘린더 이벤트 클릭 시 GA 이벤트 추적
|
||||
const handleEventClick = (info) => {
|
||||
const gtag = useGtag();
|
||||
gtag.event('calendar_event_click', {
|
||||
event_category: '이벤트캘린더',
|
||||
event_label: '캘린더 이벤트 클릭',
|
||||
value: info.event.id
|
||||
});
|
||||
const eventId = info.event.id;
|
||||
viewEventDetails(eventId);
|
||||
};
|
||||
|
||||
// 이벤트 상세 보기
|
||||
// 이벤트 상세보기 클릭 시 GA 이벤트 추적
|
||||
const viewEventDetails = async (eventId) => {
|
||||
const gtag = useGtag();
|
||||
gtag.event('view_event_details', {
|
||||
event_category: '이벤트캘린더',
|
||||
event_label: '이벤트 상세보기',
|
||||
value: eventId
|
||||
});
|
||||
try {
|
||||
selectedEvent.value = await eventService.getEventById(eventId);
|
||||
eventDetailsDialog.value = true;
|
||||
@@ -140,7 +162,13 @@ const removeParticipant = async (participant) => {
|
||||
};
|
||||
|
||||
// 이벤트 관리 페이지로 이동
|
||||
// 이벤트관리 이동 버튼 클릭 시 GA 이벤트 추적
|
||||
const navigateToEventManagement = () => {
|
||||
const gtag = useGtag();
|
||||
gtag.event('go_to_event_management', {
|
||||
event_category: '이벤트캘린더',
|
||||
event_label: '이벤트관리 이동'
|
||||
});
|
||||
router.push('/club/events');
|
||||
};
|
||||
|
||||
|
||||
@@ -122,6 +122,7 @@
|
||||
<script setup>
|
||||
import { getEventTypeLabel, getEventStatusLabel, getStatusSeverity } from '@/utils/enumMappings';
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useClubStore } from '@/stores/clubStore';
|
||||
@@ -203,7 +204,14 @@ const fetchClubMembers = async () => {
|
||||
};
|
||||
|
||||
// 컴포넌트 마운트 시 데이터 로드
|
||||
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||
onMounted(async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.pageview({
|
||||
page_title: '이벤트관리',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
if (!clubId.value) {
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
@@ -265,7 +273,14 @@ const editEvent = (eventData) => {
|
||||
};
|
||||
|
||||
// 이벤트 상세 정보 보기
|
||||
// 이벤트 상세보기 클릭 시 GA 이벤트 추적
|
||||
const viewEventDetails = async (eventData) => {
|
||||
const gtag = useGtag();
|
||||
gtag.event('view_event_details', {
|
||||
event_category: '이벤트관리',
|
||||
event_label: '이벤트 상세보기',
|
||||
value: eventData.id
|
||||
});
|
||||
try {
|
||||
const response = await eventService.getEventById(eventData.id);
|
||||
event.value = response;
|
||||
@@ -302,9 +317,16 @@ const confirmDeleteEvent = (eventData) => {
|
||||
};
|
||||
|
||||
// 이벤트 저장
|
||||
// 이벤트 저장(생성/수정) 시 GA 이벤트 추적
|
||||
const saveEvent = async (eventData) => {
|
||||
const gtag = useGtag();
|
||||
try {
|
||||
if (eventData.id) {
|
||||
gtag.event('edit_event', {
|
||||
event_category: '이벤트관리',
|
||||
event_label: '이벤트 수정',
|
||||
value: eventData.id
|
||||
});
|
||||
// 이벤트 수정
|
||||
const response = await eventService.updateEvent({
|
||||
...eventData,
|
||||
@@ -312,6 +334,10 @@ const saveEvent = async (eventData) => {
|
||||
});
|
||||
await fetchEvents(); // 목록 갱신
|
||||
} else {
|
||||
gtag.event('create_event', {
|
||||
event_category: '이벤트관리',
|
||||
event_label: '이벤트 생성'
|
||||
});
|
||||
// 이벤트 생성
|
||||
const response = await eventService.createEvent({
|
||||
...eventData,
|
||||
@@ -327,6 +353,10 @@ const saveEvent = async (eventData) => {
|
||||
});
|
||||
hideDialog();
|
||||
} catch (error) {
|
||||
gtag.event('event_save_fail', {
|
||||
event_category: '이벤트관리',
|
||||
event_label: eventData.id ? '이벤트 수정 실패' : '이벤트 생성 실패'
|
||||
});
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: '오류',
|
||||
@@ -337,17 +367,22 @@ const saveEvent = async (eventData) => {
|
||||
};
|
||||
|
||||
// 이벤트 삭제
|
||||
// 이벤트 삭제 시 GA 이벤트 추적
|
||||
const deleteEvent = async () => {
|
||||
const gtag = useGtag();
|
||||
try {
|
||||
await eventService.deleteEvent(clubId.value, event.value.id);
|
||||
|
||||
gtag.event('delete_event', {
|
||||
event_category: '이벤트관리',
|
||||
event_label: '이벤트 삭제',
|
||||
value: event.value.id
|
||||
});
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: '성공',
|
||||
detail: '이벤트가 삭제되었습니다.',
|
||||
life: 3000
|
||||
});
|
||||
|
||||
deleteEventDialog.value = false;
|
||||
const index = events.value.findIndex(e => e.id === event.value.id);
|
||||
if (index > -1) {
|
||||
@@ -355,6 +390,11 @@ const deleteEvent = async () => {
|
||||
}
|
||||
event.value = {};
|
||||
} catch (error) {
|
||||
gtag.event('delete_event_fail', {
|
||||
event_category: '이벤트관리',
|
||||
event_label: '이벤트 삭제 실패',
|
||||
value: event.value.id
|
||||
});
|
||||
toast.add({
|
||||
severity: 'error',
|
||||
summary: '오류',
|
||||
@@ -398,7 +438,13 @@ const goToScoreEntry = () => {
|
||||
};
|
||||
|
||||
// 이벤트 캘린더 페이지로 이동
|
||||
// 캘린더 이동 버튼 클릭 시 GA 이벤트 추적
|
||||
const navigateToCalendar = () => {
|
||||
const gtag = useGtag();
|
||||
gtag.event('go_to_calendar', {
|
||||
event_category: '이벤트관리',
|
||||
event_label: '캘린더 이동'
|
||||
});
|
||||
const url = `/club/calendar?clubId=${clubId.value}`;
|
||||
window.location.href = url;
|
||||
};
|
||||
|
||||
@@ -190,6 +190,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import { ref, reactive, computed, onMounted, watch } from 'vue';
|
||||
import clubService from '@/services/clubService';
|
||||
import { useClubStore } from '@/stores/clubStore';
|
||||
@@ -368,7 +369,8 @@ const loadMembers = async () => {
|
||||
|
||||
// 새 회원 추가 다이얼로그 열기
|
||||
const openNewMemberDialog = () => {
|
||||
member.value = {
|
||||
dialogMode.value = 'new';
|
||||
member.value = {
|
||||
name: '',
|
||||
gender: null,
|
||||
memberTypeObj: null,
|
||||
@@ -376,7 +378,6 @@ const openNewMemberDialog = () => {
|
||||
email: '',
|
||||
handicap: 0
|
||||
};
|
||||
dialogMode.value = 'new';
|
||||
memberDialog.value = true;
|
||||
};
|
||||
|
||||
@@ -392,6 +393,8 @@ watch(() => member.value.gender, (newGender) => {
|
||||
});
|
||||
|
||||
// 회원 수정 다이얼로그 열기
|
||||
const gtag = useGtag();
|
||||
|
||||
const editMember = (data) => {
|
||||
member.value = {
|
||||
...data
|
||||
@@ -410,12 +413,18 @@ const hideDialog = () => {
|
||||
const saveMember = async () => {
|
||||
try {
|
||||
const memberData = { ...member.value };
|
||||
|
||||
const isNew = dialogMode.value === 'new';
|
||||
const response = isNew
|
||||
? await clubService.addClubMember(clubId.value, memberData)
|
||||
: await clubService.updateClubMember(clubId.value, memberData.id, memberData);
|
||||
|
||||
// Google Analytics 이벤트 추적 - 회원 저장
|
||||
gtag.event(isNew ? 'add_member' : 'edit_member', {
|
||||
'event_category': '회원관리',
|
||||
'event_label': isNew ? '새 회원 추가' : '회원 정보 수정',
|
||||
'value': 1
|
||||
});
|
||||
|
||||
toast.add({
|
||||
severity: 'success',
|
||||
summary: '성공',
|
||||
@@ -444,6 +453,12 @@ const deleteMember = async () => {
|
||||
try {
|
||||
await clubService.deleteClubMember(clubId.value, selectedMember.value.id);
|
||||
deleteDialog.value = false;
|
||||
// Google Analytics 이벤트 추적 - 회원 삭제
|
||||
gtag.event('delete_member', {
|
||||
'event_category': '회원관리',
|
||||
'event_label': '회원 삭제',
|
||||
'value': 1
|
||||
});
|
||||
toast.add({ severity: 'success', summary: '성공', detail: '회원이 삭제되었습니다.', life: 3000 });
|
||||
await loadMembers();
|
||||
} catch (error) {
|
||||
@@ -453,6 +468,14 @@ const deleteMember = async () => {
|
||||
};
|
||||
|
||||
|
||||
onMounted(() => {
|
||||
// Google Analytics 페이지뷰 추적
|
||||
gtag.pageview({
|
||||
page_title: '회원 관리',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -199,6 +199,7 @@ import { ref, reactive, computed, onMounted } from 'vue';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import scoreService from '@/services/scoreService';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
@@ -411,7 +412,13 @@ const calculateTotal = (participant) => {
|
||||
};
|
||||
|
||||
// 모든 점수 저장
|
||||
// 점수 저장 버튼 클릭 시 GA 이벤트 추적
|
||||
const saveAllScores = async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.event('save_scores', {
|
||||
event_category: '점수관리',
|
||||
event_label: '점수 저장'
|
||||
});
|
||||
try {
|
||||
loading.value = true;
|
||||
await scoreService.saveScores(meetingId.value, participants.value);
|
||||
@@ -425,7 +432,13 @@ const saveAllScores = async () => {
|
||||
};
|
||||
|
||||
// 점수 내보내기 다이얼로그 열기
|
||||
// 점수 내보내기 버튼 클릭 시 GA 이벤트 추적
|
||||
const exportScores = () => {
|
||||
const gtag = useGtag();
|
||||
gtag.event('export_scores', {
|
||||
event_category: '점수관리',
|
||||
event_label: '점수 내보내기'
|
||||
});
|
||||
exportContent.value = [exportContentOptions.value[0]];
|
||||
exportDialog.value = true;
|
||||
};
|
||||
@@ -446,12 +459,25 @@ const downloadExport = async () => {
|
||||
};
|
||||
|
||||
// 뒤로 가기
|
||||
// 뒤로가기 버튼 클릭 시 GA 이벤트 추적
|
||||
const goBack = () => {
|
||||
const gtag = useGtag();
|
||||
gtag.event('go_back', {
|
||||
event_category: '점수관리',
|
||||
event_label: '뒤로가기'
|
||||
});
|
||||
router.push('/clubs/meetings');
|
||||
};
|
||||
|
||||
// 컴포넌트 마운트 시 데이터 로드
|
||||
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||
onMounted(async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.pageview({
|
||||
page_title: '점수관리',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
if (!clubId.value) {
|
||||
toast.add({ severity: 'error', summary: '오류', detail: '클럽 ID가 설정되지 않았습니다.', life: 3000 });
|
||||
return;
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="period-selector">
|
||||
<select v-model="selectedPeriod" @change="loadStatistics">
|
||||
<select v-model="selectedPeriod" @change="onPeriodChange">
|
||||
<option value="30">최근 30일</option>
|
||||
<option value="90">최근 90일</option>
|
||||
<option value="180">최근 180일</option>
|
||||
@@ -184,6 +184,7 @@
|
||||
|
||||
<script>
|
||||
import { ref, onMounted, computed } from 'vue';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import { Bar as BarChart, Line as LineChart } from 'vue-chartjs';
|
||||
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, LineElement, PointElement, Title, Tooltip, Legend } from 'chart.js';
|
||||
import apiClient from '@/services/api';
|
||||
@@ -194,6 +195,7 @@ export default {
|
||||
name: 'Statistics',
|
||||
components: { BarChart, LineChart },
|
||||
setup() {
|
||||
const gtag = useGtag();
|
||||
const stats = ref({
|
||||
summary: {},
|
||||
scoreDistribution: [],
|
||||
@@ -300,9 +302,25 @@ export default {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
// Google Analytics 페이지뷰 추적
|
||||
gtag.pageview({
|
||||
page_title: '통계',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
loadStatistics();
|
||||
});
|
||||
|
||||
// 기간 변경 시 GA 이벤트
|
||||
const onPeriodChange = () => {
|
||||
gtag.event('change_period', {
|
||||
'event_category': '통계',
|
||||
'event_label': selectedPeriod.value + '일',
|
||||
'value': Number(selectedPeriod.value)
|
||||
});
|
||||
loadStatistics();
|
||||
};
|
||||
|
||||
return {
|
||||
stats,
|
||||
selectedPeriod,
|
||||
@@ -314,7 +332,8 @@ export default {
|
||||
attendanceTrendChartData,
|
||||
attendanceTrendChartOptions,
|
||||
participantTrendChartData,
|
||||
getHandicapTrendChartData
|
||||
getHandicapTrendChartData,
|
||||
onPeriodChange
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -765,7 +765,14 @@ function handleGuestAdded(guest) {
|
||||
reload();
|
||||
}
|
||||
|
||||
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||
onMounted(() => {
|
||||
const gtag = useGtag();
|
||||
gtag.pageview({
|
||||
page_title: '이벤트공개페이지',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
const token = sessionStorage.getItem('event_token_' + publicHash);
|
||||
if (token) {
|
||||
fetchEventData();
|
||||
|
||||
@@ -16,13 +16,21 @@
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useGtag } from 'vue-gtag-next';
|
||||
import { useRouter, useRoute } from 'vue-router';
|
||||
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
const message = ref('');
|
||||
|
||||
// 페이지 진입 시 Google Analytics 페이지뷰 추적
|
||||
onMounted(async () => {
|
||||
const gtag = useGtag();
|
||||
gtag.pageview({
|
||||
page_title: '결제실패',
|
||||
page_path: window.location.pathname,
|
||||
page_location: window.location.href
|
||||
});
|
||||
// URL에서 메시지 가져오기
|
||||
message.value = route.query.message || '결제가 취소되었습니다.';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user