보안설정 수정, 모바일 최적화 변경
This commit is contained in:
+45
-5
@@ -25,7 +25,29 @@ const io = socketIo(server, {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.use(cors());
|
function getDomain(origin) {
|
||||||
|
try {
|
||||||
|
const url = new URL(origin);
|
||||||
|
return url.hostname; // 'lanebow.com', 'localhost'
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowedDomains = ['lanebow.com', 'localhost'];
|
||||||
|
|
||||||
|
app.use(cors({
|
||||||
|
origin: function(origin, callback) {
|
||||||
|
if (!origin) return callback(null, true);
|
||||||
|
const domain = getDomain(origin);
|
||||||
|
if (allowedDomains.includes(domain)) {
|
||||||
|
return callback(null, true);
|
||||||
|
} else {
|
||||||
|
return callback(new Error('Not allowed by CORS'), false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
credentials: true
|
||||||
|
}));
|
||||||
app.use(bodyParser.json());
|
app.use(bodyParser.json());
|
||||||
|
|
||||||
// Socket.IO 연결 처리
|
// Socket.IO 연결 처리
|
||||||
@@ -82,17 +104,35 @@ const options = {
|
|||||||
// MySQL 세션 저장소 생성
|
// MySQL 세션 저장소 생성
|
||||||
const sessionStore = new MySQLStore(options);
|
const sessionStore = new MySQLStore(options);
|
||||||
|
|
||||||
app.use(session({
|
const baseSessionOptions = {
|
||||||
key: 'bowling_session',
|
key: 'bowling_session',
|
||||||
secret: process.env.JWT_SECRET,
|
secret: process.env.JWT_SECRET,
|
||||||
store: sessionStore,
|
store: sessionStore,
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false,
|
saveUninitialized: false,
|
||||||
cookie: {
|
cookie: {
|
||||||
secure: process.env.NODE_ENV === 'production',
|
maxAge: 24 * 60 * 60 * 1000,
|
||||||
maxAge: 24 * 60 * 60 * 1000 // 1일
|
secure: false,
|
||||||
|
sameSite: 'lax'
|
||||||
}
|
}
|
||||||
}));
|
};
|
||||||
|
app.use(session(baseSessionOptions));
|
||||||
|
|
||||||
|
// 요청마다 secure/sameSite를 동적으로 할당
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
const origin = req.headers.origin;
|
||||||
|
const domain = getDomain(origin);
|
||||||
|
if (req.session && req.session.cookie) {
|
||||||
|
if (domain === 'lanebow.com') {
|
||||||
|
req.session.cookie.secure = true;
|
||||||
|
req.session.cookie.sameSite = 'none';
|
||||||
|
} else {
|
||||||
|
req.session.cookie.secure = false;
|
||||||
|
req.session.cookie.sameSite = 'lax';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
// 데이터베이스 연결 및 모델 동기화 실행
|
// 데이터베이스 연결 및 모델 동기화 실행
|
||||||
sequelize.authenticate()
|
sequelize.authenticate()
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ exports.selectClub = async (req, res) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
req.session.clubId = clubId;
|
req.session.clubId = clubId;
|
||||||
|
console.log(`Session clubId: ${clubId}`);
|
||||||
res.status(200).json({ message: '클럽을 선택했습니다.' });
|
res.status(200).json({ message: '클럽을 선택했습니다.' });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('클럽 선택 오류:', error);
|
console.error('클럽 선택 오류:', error);
|
||||||
@@ -136,18 +137,18 @@ exports.getClubById = async (req, res) => {
|
|||||||
// 새 클럽 생성
|
// 새 클럽 생성
|
||||||
exports.createClub = async (req, res) => {
|
exports.createClub = async (req, res) => {
|
||||||
try {
|
try {
|
||||||
const { name, location, description, ownerEmail, contact, features, sendInvite } = req.body;
|
const { name, location, description, averageCalculationPeriod } = req.body;
|
||||||
|
|
||||||
// 필수 필드 검증
|
// 필수 필드 검증
|
||||||
if (!name || !ownerEmail) {
|
if (!name) {
|
||||||
return res.status(400).json({ message: '클럽 이름과 모임장 이메일은 필수 항목입니다.' });
|
return res.status(400).json({ message: '클럽 이름은 필수 항목입니다.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 이메일로 사용자 조회
|
// 이메일로 사용자 조회
|
||||||
const owner = await User.findOne({ where: { email: ownerEmail } });
|
const owner = await User.findOne({ where: { id: req.user.id } });
|
||||||
|
|
||||||
if (!owner) {
|
if (!owner) {
|
||||||
return res.status(404).json({ message: '해당 이메일로 등록된 사용자를 찾을 수 없습니다.' });
|
return res.status(404).json({ message: '해당 사용자를 찾을 수 없습니다.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// 클럽 생성
|
// 클럽 생성
|
||||||
@@ -155,7 +156,7 @@ exports.createClub = async (req, res) => {
|
|||||||
name,
|
name,
|
||||||
location,
|
location,
|
||||||
description,
|
description,
|
||||||
contact,
|
averageCalculationPeriod,
|
||||||
ownerId: owner.id,
|
ownerId: owner.id,
|
||||||
memberCount: 1, // 모임장 포함
|
memberCount: 1, // 모임장 포함
|
||||||
status: 'active'
|
status: 'active'
|
||||||
@@ -172,25 +173,6 @@ exports.createClub = async (req, res) => {
|
|||||||
status: 'active'
|
status: 'active'
|
||||||
});
|
});
|
||||||
|
|
||||||
// 선택된 기능 추가
|
|
||||||
if (features && features.length > 0) {
|
|
||||||
const featurePromises = features.map(featureId =>
|
|
||||||
ClubFeature.create({
|
|
||||||
clubId: newClub.id,
|
|
||||||
featureId,
|
|
||||||
enabled: true
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
await Promise.all(featurePromises);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 초대 이메일 발송 로직 (sendInvite가 true인 경우)
|
|
||||||
if (sendInvite) {
|
|
||||||
// TODO: 초대 이메일 발송 로직 구현
|
|
||||||
console.log(`${ownerEmail}에게 초대 이메일 발송 예정`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 베이직 1개월 플랜 자동 추가
|
// 베이직 1개월 플랜 자동 추가
|
||||||
try {
|
try {
|
||||||
// 베이직 플랜 조회 (ID가 1이라고 가정, 실제 환경에 맞게 조정 필요)
|
// 베이직 플랜 조회 (ID가 1이라고 가정, 실제 환경에 맞게 조정 필요)
|
||||||
|
|||||||
+26
-23
@@ -1,14 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<div id="app">
|
||||||
|
<AppHeader @toggle-sidebar="sidebarVisible = !sidebarVisible" />
|
||||||
<Toast />
|
<Toast />
|
||||||
<template v-if="isRouterReady">
|
<template v-if="isRouterReady">
|
||||||
<AppPublicLayout v-if="isPublicLayout">
|
<AppPublicLayout v-if="isPublicLayout">
|
||||||
<router-view />
|
<router-view />
|
||||||
</AppPublicLayout>
|
</AppPublicLayout>
|
||||||
<div v-else class="app">
|
<div v-else class="app">
|
||||||
<AppHeader />
|
|
||||||
<div class="app-content">
|
<div class="app-content">
|
||||||
<div v-if="isSidebarVisible && isMobileView" class="sidebar-overlay" @click="closeSidebar"></div>
|
<div v-if="sidebarVisible && isMobileView" class="sidebar-overlay" @click="closeSidebar"></div>
|
||||||
<aside v-if="isLoggedIn" class="sidebar" :class="{ 'mobile-visible': isSidebarVisible, 'mobile-hidden': !isSidebarVisible && isMobileView }">
|
<aside v-if="isLoggedIn" class="sidebar" :class="{ 'mobile-visible': sidebarVisible, 'mobile-hidden': !sidebarVisible && isMobileView }">
|
||||||
<div class="sidebar-content">
|
<div class="sidebar-content">
|
||||||
<!-- 클럽 정보 -->
|
<!-- 클럽 정보 -->
|
||||||
<div class="club-info" v-if="selectedClub">
|
<div class="club-info" v-if="selectedClub">
|
||||||
@@ -43,7 +44,7 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<!-- 커스텀 사이드바 메뉴 -->
|
<!-- 커스텀 사이드바 메뉴 -->
|
||||||
<div class="custom-sidebar-menu">
|
<div class="custom-sidebar-menu" :class="{ mobile: isMobileView }" v-show="sidebarVisible">
|
||||||
<template v-for="(menuGroup, groupIndex) in menuItems" :key="groupIndex">
|
<template v-for="(menuGroup, groupIndex) in menuItems" :key="groupIndex">
|
||||||
<div class="menu-group">
|
<div class="menu-group">
|
||||||
<div class="menu-group-header" @click="toggleMenuGroup(groupIndex)">
|
<div class="menu-group-header" @click="toggleMenuGroup(groupIndex)">
|
||||||
@@ -68,7 +69,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<!-- 그룹이 없는 단일 메뉴 아이템 -->
|
<!-- 그룹이 없는 단일 메뉴 아이템 -->
|
||||||
<template v-for="(menuItem, itemIndex) in singleMenuItems" :key="'single-'+itemIndex">
|
<div v-for="(menuItem, itemIndex) in singleMenuItems" :key="'single-'+itemIndex">
|
||||||
<router-link
|
<router-link
|
||||||
:to="menuItem.to"
|
:to="menuItem.to"
|
||||||
class="menu-item single-menu-item"
|
class="menu-item single-menu-item"
|
||||||
@@ -79,7 +80,7 @@
|
|||||||
<i :class="menuItem.icon"></i>
|
<i :class="menuItem.icon"></i>
|
||||||
<span>{{ menuItem.label }}</span>
|
<span>{{ menuItem.label }}</span>
|
||||||
</router-link>
|
</router-link>
|
||||||
</template>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
@@ -92,6 +93,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
@@ -120,7 +122,7 @@ const menuItems = ref([]);
|
|||||||
const singleMenuItems = ref([]);
|
const singleMenuItems = ref([]);
|
||||||
const expandedGroups = ref({});
|
const expandedGroups = ref({});
|
||||||
const isMobileView = ref(window.innerWidth < 768);
|
const isMobileView = ref(window.innerWidth < 768);
|
||||||
const isSidebarVisible = ref(!isMobileView.value);
|
const sidebarVisible = ref(window.innerWidth > 600);
|
||||||
const isLoggedIn = computed(() => !!user.value && !!localStorage.getItem('token'));
|
const isLoggedIn = computed(() => !!user.value && !!localStorage.getItem('token'));
|
||||||
const clubs = ref([]); // 클럽 목록
|
const clubs = ref([]); // 클럽 목록
|
||||||
const selectedClubId = ref(localStorage.getItem('clubId') || null); // 선택된 클럽 ID
|
const selectedClubId = ref(localStorage.getItem('clubId') || null); // 선택된 클럽 ID
|
||||||
@@ -135,20 +137,18 @@ const userClubRole = computed(() => localStorage.getItem('userClubRole') || '');
|
|||||||
// 화면 크기 변경 감지
|
// 화면 크기 변경 감지
|
||||||
const handleResize = () => {
|
const handleResize = () => {
|
||||||
isMobileView.value = window.innerWidth < 768;
|
isMobileView.value = window.innerWidth < 768;
|
||||||
if (!isMobileView.value) {
|
sidebarVisible.value = window.innerWidth > 600;
|
||||||
isSidebarVisible.value = true;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 사이드바 토글
|
// 사이드바 토글
|
||||||
const toggleSidebar = () => {
|
const toggleSidebar = () => {
|
||||||
isSidebarVisible.value = !isSidebarVisible.value;
|
sidebarVisible.value = !sidebarVisible.value;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 사이드바 닫기
|
// 사이드바 닫기
|
||||||
const closeSidebar = () => {
|
const closeSidebar = () => {
|
||||||
if (isMobileView.value) {
|
if (isMobileView.value) {
|
||||||
isSidebarVisible.value = false;
|
sidebarVisible.value = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -312,17 +312,7 @@ const loadMenuItems = async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
menuItems.value = groupMenus;
|
menuItems.value = groupMenus;
|
||||||
/*
|
singleMenuItems.value = singleMenus;
|
||||||
// 구독관리 메뉴 하드코딩으로 추가
|
|
||||||
singleMenuItems.value = [
|
|
||||||
...singleMenus,
|
|
||||||
{
|
|
||||||
label: '구독관리',
|
|
||||||
icon: 'pi pi-credit-card',
|
|
||||||
to: '/club/subscription'
|
|
||||||
}
|
|
||||||
];
|
|
||||||
*/
|
|
||||||
} else {
|
} else {
|
||||||
menuItems.value = [];
|
menuItems.value = [];
|
||||||
singleMenuItems.value = [];
|
singleMenuItems.value = [];
|
||||||
@@ -534,6 +524,17 @@ body {
|
|||||||
.custom-sidebar-menu {
|
.custom-sidebar-menu {
|
||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
|
.custom-sidebar-menu.mobile {
|
||||||
|
position: fixed;
|
||||||
|
left: 0;
|
||||||
|
width: 200px;
|
||||||
|
height: 100%;
|
||||||
|
z-index: 1200;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 2px 0 8px rgba(0,0,0,0.1);
|
||||||
|
transition: left 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.menu-group-header {
|
.menu-group-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -602,9 +603,11 @@ body {
|
|||||||
|
|
||||||
/* 메인 컨텐츠 스타일 */
|
/* 메인 컨텐츠 스타일 */
|
||||||
.main-content {
|
.main-content {
|
||||||
|
width: 100%;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
transition: margin-left 0.3s ease;
|
transition: margin-left 0.3s ease;
|
||||||
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.main-content.full-width {
|
.main-content.full-width {
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<header class="app-header">
|
<header class="app-header">
|
||||||
|
<button class="sidebar-toggle" @click="$emit('toggle-sidebar')">
|
||||||
|
<i class="pi pi-bars"></i>
|
||||||
|
</button>
|
||||||
<div class="logo">
|
<div class="logo">
|
||||||
<img src="@/assets/LaneBowLogo.png" alt="LaneBow Logo" class="logo-img" />
|
<img src="@/assets/LaneBowLogo.png" alt="LaneBow Logo" class="logo-img header-logo" />
|
||||||
<div class="app-title">볼링 클럽 매니저</div>
|
<div class="app-title">볼링 클럽 매니저</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -524,4 +527,30 @@ onBeforeUnmount(() => {
|
|||||||
margin: 0.5rem;
|
margin: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.header-logo {
|
||||||
|
height: 60px;
|
||||||
|
transition: height 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-toggle {
|
||||||
|
display: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 2rem;
|
||||||
|
margin-right: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.header-logo {
|
||||||
|
height: 36px;
|
||||||
|
}
|
||||||
|
.app-title {
|
||||||
|
font-size: .8rem;
|
||||||
|
margin-top: -10px;
|
||||||
|
}
|
||||||
|
.sidebar-toggle {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const apiClient = axios.create({
|
|||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
|
withCredentials: true,
|
||||||
timeout: 10000
|
timeout: 10000
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -170,4 +170,30 @@ const formatDate = (date) => {
|
|||||||
:deep(.p-datatable-wrapper) {
|
:deep(.p-datatable-wrapper) {
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.activity-log-container, .header, .dashboard-card, .subscription-management-container, .user-management-container, .club-management {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
margin-bottom: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.dashboard-card {
|
||||||
|
padding: 0.7rem !important;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.header h2, .dashboard-card h3 {
|
||||||
|
font-size: 1.1rem !important;
|
||||||
|
}
|
||||||
|
.p-datatable-wrapper {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
}
|
||||||
|
/* Dialog 팝업 중앙 정렬 개선 */
|
||||||
|
:deep(.p-dialog) {
|
||||||
|
align-items: flex-start !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
padding-top: 2.5rem !important;
|
||||||
|
}
|
||||||
|
:deep(.p-dialog .p-dialog-content) {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -725,4 +725,35 @@ const saveMember = async () => {
|
|||||||
color: #666;
|
color: #666;
|
||||||
line-height: 1.2;
|
line-height: 1.2;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.club-management, .header, .dashboard-card, .activity-log-container, .subscription-management-container, .user-management-container {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
margin-bottom: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.dashboard-card {
|
||||||
|
padding: 0.7rem !important;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.header h2, .dashboard-card h3 {
|
||||||
|
font-size: 1.1rem !important;
|
||||||
|
}
|
||||||
|
.features-grid, .plan-details {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
gap: 0.7rem !important;
|
||||||
|
}
|
||||||
|
.p-datatable-wrapper {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
}
|
||||||
|
/* Dialog 팝업 중앙 정렬 개선 */
|
||||||
|
:deep(.p-dialog) {
|
||||||
|
align-items: flex-start !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
padding-top: 2.5rem !important;
|
||||||
|
}
|
||||||
|
:deep(.p-dialog .p-dialog-content) {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -303,4 +303,30 @@ const navigateTo = (path) => {
|
|||||||
:deep(.p-datatable .p-datatable-tbody > tr) {
|
:deep(.p-datatable .p-datatable-tbody > tr) {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.admin-dashboard, .header, .dashboard-card, .activity-log-container, .subscription-management-container, .user-management-container, .club-management {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
margin-bottom: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.dashboard-card {
|
||||||
|
padding: 0.7rem !important;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.header h2, .dashboard-card h3 {
|
||||||
|
font-size: 1.1rem !important;
|
||||||
|
}
|
||||||
|
.p-datatable-wrapper {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
}
|
||||||
|
/* Dialog 팝업 중앙 정렬 개선 */
|
||||||
|
:deep(.p-dialog) {
|
||||||
|
align-items: flex-start !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
padding-top: 2.5rem !important;
|
||||||
|
}
|
||||||
|
:deep(.p-dialog .p-dialog-content) {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -16,10 +16,9 @@
|
|||||||
dataKey="id"
|
dataKey="id"
|
||||||
@row-expand="onPlanExpand"
|
@row-expand="onPlanExpand"
|
||||||
>
|
>
|
||||||
<Column :expander="true" style="width: 3%"/>
|
<Column :expander="true" style="width: 1%"/>
|
||||||
<Column field="id" header="ID" sortable style="width: 5%"></Column>
|
<Column field="id" header="ID" sortable style="width: 5%"></Column>
|
||||||
<Column field="name" header="플랜명" sortable style="width: 15%"></Column>
|
<Column field="name" header="플랜명" sortable></Column>
|
||||||
<Column field="description" header="설명" style="width: 25%"></Column>
|
|
||||||
<Column field="maxMembers" header="최대 회원수" sortable style="width: 10%">
|
<Column field="maxMembers" header="최대 회원수" sortable style="width: 10%">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
{{ slotProps.data.maxMembers }}명
|
{{ slotProps.data.maxMembers }}명
|
||||||
@@ -30,7 +29,7 @@
|
|||||||
{{ formatCurrency(slotProps.data.price) }}
|
{{ formatCurrency(slotProps.data.price) }}
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="status" header="상태" sortable style="width: 10%">
|
<Column field="status" header="상태" sortable style="width: 15%">
|
||||||
<template #body="slotProps">
|
<template #body="slotProps">
|
||||||
<Badge :value="getStatusLabel(slotProps.data.status)" :severity="getStatusSeverity(slotProps.data.status)" />
|
<Badge :value="getStatusLabel(slotProps.data.status)" :severity="getStatusSeverity(slotProps.data.status)" />
|
||||||
</template>
|
</template>
|
||||||
@@ -45,6 +44,10 @@
|
|||||||
</Column>
|
</Column>
|
||||||
<template #expansion="slotProps">
|
<template #expansion="slotProps">
|
||||||
<div class="plan-details">
|
<div class="plan-details">
|
||||||
|
<div class="plan-description">
|
||||||
|
<h4>설명</h4>
|
||||||
|
<p>{{ slotProps.data.description }}</p>
|
||||||
|
</div>
|
||||||
<div class="plan-features">
|
<div class="plan-features">
|
||||||
<h4>포함된 기능</h4>
|
<h4>포함된 기능</h4>
|
||||||
<div class="features-grid">
|
<div class="features-grid">
|
||||||
@@ -603,4 +606,35 @@ const formatDate = (date) => {
|
|||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
padding: 0.3rem 0.5rem;
|
padding: 0.3rem 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.subscription-management-container, .header, .dashboard-card, .activity-log-container, .user-management-container, .club-management {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
margin-bottom: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.dashboard-card {
|
||||||
|
padding: 0.7rem !important;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.header h2, .dashboard-card h3 {
|
||||||
|
font-size: 1.1rem !important;
|
||||||
|
}
|
||||||
|
.features-grid, .plan-details {
|
||||||
|
grid-template-columns: 1fr !important;
|
||||||
|
flex-direction: column !important;
|
||||||
|
gap: 0.7rem !important;
|
||||||
|
}
|
||||||
|
.p-datatable-wrapper {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
}
|
||||||
|
/* Dialog 팝업 중앙 정렬 개선 */
|
||||||
|
:deep(.p-dialog) {
|
||||||
|
align-items: flex-start !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
padding-top: 2.5rem !important;
|
||||||
|
}
|
||||||
|
:deep(.p-dialog .p-dialog-content) {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -294,4 +294,30 @@ const formatDate = (date) => {
|
|||||||
:deep(.p-password-input) {
|
:deep(.p-password-input) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@media (max-width: 600px) {
|
||||||
|
.user-management-container, .header, .dashboard-card, .activity-log-container, .subscription-management-container, .club-management {
|
||||||
|
padding: 0.5rem !important;
|
||||||
|
margin-bottom: 0.5rem !important;
|
||||||
|
}
|
||||||
|
.dashboard-card {
|
||||||
|
padding: 0.7rem !important;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
.header h2, .dashboard-card h3 {
|
||||||
|
font-size: 1.1rem !important;
|
||||||
|
}
|
||||||
|
.p-datatable-wrapper {
|
||||||
|
overflow-x: auto !important;
|
||||||
|
}
|
||||||
|
/* Dialog 팝업 중앙 정렬 개선 */
|
||||||
|
:deep(.p-dialog) {
|
||||||
|
align-items: flex-start !important;
|
||||||
|
justify-content: center !important;
|
||||||
|
padding-top: 2.5rem !important;
|
||||||
|
}
|
||||||
|
:deep(.p-dialog .p-dialog-content) {
|
||||||
|
margin-top: 0 !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user