버그 수정, 모임장 설정 수정

This commit is contained in:
2025-05-31 08:13:36 +09:00
parent 27fbf0541d
commit e609a8ce52
9 changed files with 314 additions and 105 deletions
+14 -1
View File
@@ -15,7 +15,7 @@
<div class="club-header">
<div class="club-title">
<h3>{{ selectedClub.name }}</h3>
<router-link v-if="userRole === 'admin' || userRole === '모임장' || userRole === '운영진'"
<router-link v-if="userRole === 'admin' || userClubRole === '모임장'"
:to="{ name: 'ClubSettings' }"
class="settings-icon"
title="클럽 설정">
@@ -340,6 +340,19 @@ const loadMenuItems = async () => {
}
};
// 사이드바 새로고침 메소드
const refreshSidebar = async () => {
console.log('사이드바 새로고침 시작...');
await fetchClubs();
await fetchClubInfo();
loadMenuItems();
initializeExpandedGroups(); // 메뉴 그룹 초기화
console.log('사이드바 새로고침 완료');
};
// 사이드바 새로고침 메소드를 전역으로 노출
provide('refreshSidebar', refreshSidebar);
// 사용자 정보 로드
const loadUserData = async () => {
const token = localStorage.getItem('token');
+18 -3
View File
@@ -47,6 +47,13 @@
</div>
</div>
</div>
<!-- 클럽 추가 버튼 -->
<div class="club-add-wrapper">
<Button icon="pi pi-plus-circle" class="club-add-button p-button-outlined" @click="goToClubCreate"
title="새 클럽 만들기" />
</div>
<!-- 사용자 메뉴 -->
<div class="user-menu-wrapper">
<Button @click="toggleUserMenu" class="user-menu-button p-button-outlined">
@@ -301,6 +308,11 @@ const toggleNotifications = (event) => {
event.stopPropagation();
};
// 클럽 생성 페이지로 이동
const goToClubCreate = () => {
router.push({ name: 'UserClubCreate' });
};
// 문서 클릭 시 알림 패널 닫기
const handleDocumentClick = (event) => {
const notificationWrapper = document.querySelector('.notification-wrapper');
@@ -348,13 +360,15 @@ onBeforeUnmount(() => {
.user-menu-wrapper,
.admin-menu-wrapper,
.notification-wrapper {
.notification-wrapper,
.club-add-wrapper {
position: relative;
}
.user-menu-button,
.admin-menu-button,
.notification-button {
.notification-button,
.club-add-button {
background-color: transparent !important;
border: 1px solid rgba(255, 255, 255, 0.3) !important;
color: white !important;
@@ -362,7 +376,8 @@ onBeforeUnmount(() => {
.user-menu-button:hover,
.admin-menu-button:hover,
.notification-button:hover {
.notification-button:hover,
.club-add-button:hover {
background-color: rgba(255, 255, 255, 0.1) !important;
}
+3 -1
View File
@@ -85,7 +85,7 @@ const clubService = {
...memberData
});
},
/**
* 클럽 회원 삭제
* @param {Number} clubId - 클럽 ID
@@ -95,6 +95,8 @@ const clubService = {
deleteClubMember(clubId, memberId) {
return apiClient.post('/api/club/members/delete', { clubId, memberId });
},
/**
* 클럽 회원 통계 조회
+12 -1
View File
@@ -34,7 +34,7 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { ref, computed, onMounted, inject } from 'vue';
import { useRouter } from 'vue-router';
import { useToast } from 'primevue/usetoast';
import clubService from '@/services/clubService';
@@ -44,6 +44,7 @@ import axios from 'axios'
const toast = useToast();
const authStore = useAuthStore();
const router = useRouter();
const refreshSidebar = inject('refreshSidebar'); // App.vue에서 제공하는 사이드바 새로고침 메소드 주입
const clubName = ref('');
const clubDescription = ref('');
@@ -106,6 +107,16 @@ const handleCreateClub = async () => {
// 성공 메시지
toast.add({ severity: 'success', summary: '성공', detail: '클럽이 성공적으로 생성되었습니다.', life: 3000 });
// 사이드바 새로고침
if (refreshSidebar) {
try {
await refreshSidebar();
console.log('클럽 생성 후 사이드바 새로고침 완료');
} catch (refreshError) {
console.error('사이드바 새로고침 오류:', refreshError);
}
}
// 클럽 페이지로 이동
router.push('/club');
} catch (error) {
+90 -21
View File
@@ -27,6 +27,15 @@
<input type="number" id="femaleHandicap" v-model="clubInfo.femaleHandicap" class="form-control" min="0" max="100" />
<small class="form-text text-muted">여성 회원을 멤버(게스트) 등록할 기본 핸디캡 값입니다.</small>
</div>
<div class="form-group">
<label for="clubOwner">모임장 설정</label>
<select id="clubOwner" v-model="selectedOwnerId" class="form-control">
<option v-for="member in members" :key="member.id" :value="member.userId">
{{ member.name }} ({{ member.memberType }}) [ID: {{ member.userId }}]
</option>
</select>
<small class="form-text text-muted">모임장 권한을 이전할 회원을 선택합니다. 모임장은 클럽의 모든 관리 권한을 가집니다.</small>
</div>
<div class="form-actions">
<button @click="saveClubInfo" class="btn btn-primary">저장</button>
</div>
@@ -111,7 +120,7 @@
</template>
<script setup>
import { ref, onMounted, computed } from 'vue';
import { ref, onMounted, computed, inject } from 'vue';
import { useRouter } from 'vue-router';
import { useToast } from 'primevue/usetoast';
import { useClubStore } from '@/stores/clubStore';
@@ -121,6 +130,7 @@ const clubStore = useClubStore();
const router = useRouter();
const toast = useToast();
const refreshSidebar = inject('refreshSidebar'); // 사이드바 새로고침 메소드 주입
// 클럽 정보
const clubInfo = ref({
@@ -128,15 +138,21 @@ const clubInfo = ref({
name: '',
description: '',
location: '',
femaleHandicap: 15 // 기본값 설정
femaleHandicap: 15, // 기본값 설정
ownerId: null // 모임장 ID
});
// 구독 정보
const subscriptionInfo = ref(null);
const clubId = localStorage.getItem('clubId');
// 기능 목록
// 기능 정보
const features = ref([]);
// 회원 목록
const members = ref([]);
const selectedOwnerId = ref(null); // 선택된 모임장 ID
// 구독 상태 텍스트 계산
const subscriptionStatusText = computed(() => {
if (!subscriptionInfo.value) return '없음';
@@ -179,9 +195,8 @@ const formatDate = (dateString) => {
// 클럽 정보 로드
const loadClubInfo = async () => {
try {
const clubId = localStorage.getItem('clubId');
if (!clubId) {
toast.add({ severity: 'error', summary: '오류', detail: '클럽 정보를 찾을 수 없습니다.', life: 3000 });
router.push('/club');
return;
}
@@ -192,17 +207,19 @@ const loadClubInfo = async () => {
description: clubData.description || '',
location: clubData.location || '',
memberCount: clubData.memberCount || 0,
femaleHandicap: clubData.femaleHandicap || 15
femaleHandicap: clubData.femaleHandicap || 15,
ownerId: clubData.ownerId // 모임장 ID 추가
};
// 구독 정보 로드
await loadSubscriptionInfo(clubId);
// 기능 정보 로드
// 선택된 모임장 ID 설정
selectedOwnerId.value = clubData.ownerId;
// 구독 정보 및 기능 정보 로드
await loadSubscriptionInfo(clubId);
await loadFeatures(clubId);
} catch (error) {
console.error('클럽 정보 로드 오류:', error);
toast.add({ severity: 'error', summary: '오류', detail: '클럽 정보를 불러오는 중 오류가 발생했습니다.', life: 3000 });
toast.add({ severity: 'error', summary: '오류', detail: '클럽 정보를 가져오는 중 오류가 발생했습니다.', life: 3000 });
}
};
@@ -283,34 +300,86 @@ const getFeatureIcon = (featureType) => {
// 클럽 정보 저장
const saveClubInfo = async () => {
try {
const clubId = clubInfo.value.id;
if (!clubId) {
toast.add({ severity: 'error', summary: '오류', detail: '클럽 정보를 찾을 수 없습니다.', life: 3000 });
router.push('/club');
return;
}
// clubService를 사용하여 클럽 정보 업데이트
await clubService.updateClub(clubId, {
// 모임장 변경 확인
const isOwnerChanged = selectedOwnerId.value !== clubInfo.value.ownerId;
let updateData = {
name: clubInfo.value.name,
description: clubInfo.value.description,
location: clubInfo.value.location,
femaleHandicap: clubInfo.value.femaleHandicap
});
};
// 모임장이 변경되었다면 확인 메시지 표시
if (isOwnerChanged) {
const confirmChange = confirm('모임장을 변경하시겠습니까? 모임장 권한이 새로운 회원에게 이전됩니다.');
if (!confirmChange) {
// 선택을 원래 모임장으로 되돌림
selectedOwnerId.value = clubInfo.value.ownerId;
return;
}
// 모임장 ID 추가
updateData.ownerId = selectedOwnerId.value;
}
// clubService를 사용하여 클럽 정보 업데이트
await clubService.updateClub(clubId, updateData);
// 성공 메시지 표시
toast.add({ severity: 'success', summary: '성공', detail: '클럽 정보가 저장되었습니다.', life: 3000 });
let successMessage = '클럽 정보가 저장되었습니다.';
if (isOwnerChanged) {
successMessage = '클럽 정보가 저장되었으며, 모임장이 변경되었습니다.';
}
// 클럽 정보 다시 불러오기
toast.add({ severity: 'success', summary: '성공', detail: successMessage, life: 3000 });
// 클럽 정보와 회원 목록 다시 불러오기
await loadClubInfo();
await loadMembers();
// 사이드바 새로고침
if (refreshSidebar) {
try {
await refreshSidebar();
} catch (refreshError) {
console.error('사이드바 새로고침 오류:', refreshError);
}
}
} catch (error) {
console.error('클럽 정보 저장 오류:', error);
toast.add({ severity: 'error', summary: '오류', detail: '클럽 정보를 저장하는 중 오류가 발생했습니다.', life: 3000 });
}
};
// 회원 목록 가져오기
const loadMembers = async () => {
try {
const response = await clubService.getClubMembers(clubId);
if (response.data) {
const data = response.data;
members.value = data;
// 현재 모임장 정보 설정
if (clubInfo.value && clubInfo.value.ownerId) {
selectedOwnerId.value = clubInfo.value.ownerId;
}
}
} catch (error) {
console.error('회원 목록 가져오기 오류:', error);
toast.add({ severity: 'error', summary: '오류', detail: '회원 목록을 가져오는 중 오류가 발생했습니다.', life: 3000 });
}
};
// 컴포넌트 마운트 시 데이터 로드
onMounted(() => {
loadClubInfo();
onMounted(async () => {
await loadClubInfo();
await loadMembers();
});
</script>
+5 -5
View File
@@ -85,7 +85,7 @@
</div>
<div class="field col-6 mb-3">
<label for="memberType" class="mb-1">회원 유형</label>
<Select id="memberType" v-model="member.memberType" :options="memberTypes" optionLabel="name" optionValue="code" placeholder="회원 유형 선택" class="w-full" />
<Select id="memberType" v-model="member.memberType" :options="memberTypeOptions" optionLabel="name" optionValue="code" placeholder="회원 유형 선택" class="w-full" />
</div>
<div class="field col-6 mb-3">
<label for="phone" class="mb-1">전화번호</label>
@@ -194,12 +194,12 @@ import { useToast } from 'primevue/usetoast';
const clubStore = useClubStore();
const toast = useToast();
// 회원 유형 목록
const memberTypes = ref([
// 회원 유형 옵션
const memberTypeOptions = ref([
{ name: '정회원', code: '정회원' },
{ name: '준회원', code: '준회원' },
{ name: '운영진', code: '운영진' },
{ name: '모임장', code: '모임장' }
{ name: '운영진', code: '운영진' }
// 모임장 옵션은 제거 - 클럽 설정에서만 변경 가능하도록 제한
]);
// 성별 옵션
@@ -1097,6 +1097,10 @@ const processPayment = async () => {
// 토스페이먼츠 SDK 초기화
const tossPayments = await loadTossPaymentsSDK();
// 결제 성공/실패 핸들러 등록 (전역 함수로 등록)
window.handlePaymentSuccess = handlePaymentSuccess;
window.handlePaymentFail = handlePaymentFail;
// 결제 창 열기 - 결제 방법을 미리 지정하지 않고 토스페이먼츠 결제 창에서 선택하도록 함
await tossPayments.requestPayment({
...response.data.paymentData,
@@ -1118,6 +1122,21 @@ const processPayment = async () => {
}
};
// 선택 항목 초기화
const resetSelections = () => {
selectedPlan.value = null;
selectedFeatures.value = [];
selectedPlanDuration.value = 1;
showPlans.value = false;
showFeatures.value = false;
isSelectingPlan.value = false;
isSelectingFeatures.value = false;
changeOption.value = 'immediate';
paymentAgreement.value = false;
showPaymentSection.value = false;
showAdditionalFeatures.value = false;
};
// 토스페이먼츠 SDK 로드
const loadTossPaymentsSDK = () => {
return new Promise((resolve, reject) => {
@@ -1153,6 +1172,8 @@ const loadTossPaymentsSDK = () => {
// 결제 성공 처리
const handlePaymentSuccess = async (orderId) => {
try {
console.log('결제 성공:', orderId);
// 데이터 다시 로드
await Promise.all([
loadCurrentSubscription(),
@@ -1162,23 +1183,40 @@ const handlePaymentSuccess = async (orderId) => {
]);
// 상태 초기화
selectedPlan.value = null;
selectedFeatures.value = [];
paymentAgreement.value = false;
showPaymentSection.value = false;
showPlans.value = false;
showAdditionalFeatures.value = false;
changeOption.value = isUpgrade.value ? 'immediate' : 'nextBilling';
resetSelections();
alert('결제가 성공적으로 완료되었습니다.');
// 성공 메시지 표시 (토스트 메시지 추가)
toast.add({
severity: 'success',
summary: '결제 성공',
detail: '구독 플랜 결제가 성공적으로 완료되었습니다.',
life: 5000
});
} catch (error) {
console.error('결제 성공 후 처리 오류:', error);
toast.add({
severity: 'error',
summary: '오류',
detail: '구독 정보를 새로고침하는 중 오류가 발생했습니다.',
life: 5000
});
}
};
// 결제 실패 처리
const handlePaymentFail = (message) => {
alert(`결제 실패: ${message || '사용자가 결제를 취소했습니다.'}`);
console.log('결제 실패:', message);
// 실패 메시지 표시 (토스트 메시지 추가)
toast.add({
severity: 'error',
summary: '결제 실패',
detail: message || '결제 처리 중 오류가 발생했습니다.',
life: 5000
});
// 결제 실패 후 초기화
resetSelections();
};
// 플랜 구독 (기존 기능 - 레거시)