회원목록
This commit is contained in:
@@ -0,0 +1,456 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../services/admin_service.dart';
|
||||
import '../../services/subscription_service.dart';
|
||||
import '../../models/subscription_model.dart';
|
||||
|
||||
/// 관리자 대시보드 화면
|
||||
class AdminDashboardScreen extends StatefulWidget {
|
||||
const AdminDashboardScreen({super.key});
|
||||
|
||||
@override
|
||||
State<AdminDashboardScreen> createState() => _AdminDashboardScreenState();
|
||||
}
|
||||
|
||||
class _AdminDashboardScreenState extends State<AdminDashboardScreen> {
|
||||
Map<String, dynamic>? _analyticsData;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadData();
|
||||
}
|
||||
|
||||
/// 데이터 로드
|
||||
Future<void> _loadData() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final adminService = Provider.of<AdminService>(context, listen: false);
|
||||
final analyticsData = await adminService.fetchClubAnalytics();
|
||||
|
||||
setState(() {
|
||||
_analyticsData = analyticsData;
|
||||
});
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('데이터 로드 중 오류가 발생했습니다: $e')),
|
||||
);
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final adminService = Provider.of<AdminService>(context);
|
||||
final subscriptionService = Provider.of<SubscriptionService>(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('관리자 대시보드'),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: _loadData,
|
||||
),
|
||||
],
|
||||
),
|
||||
body: _isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
: RefreshIndicator(
|
||||
onRefresh: _loadData,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildPermissionsCard(adminService),
|
||||
const SizedBox(height: 16),
|
||||
_buildSubscriptionCard(subscriptionService),
|
||||
const SizedBox(height: 16),
|
||||
_buildAnalyticsCard(),
|
||||
const SizedBox(height: 16),
|
||||
_buildQuickActionsCard(context, adminService),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 권한 정보 카드
|
||||
Widget _buildPermissionsCard(AdminService adminService) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.admin_panel_settings),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'관리자 권한',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
_buildPermissionItem(
|
||||
'회원 관리',
|
||||
adminService.canManageMembers,
|
||||
Icons.people,
|
||||
),
|
||||
_buildPermissionItem(
|
||||
'이벤트 관리',
|
||||
adminService.canManageEvents,
|
||||
Icons.event,
|
||||
),
|
||||
_buildPermissionItem(
|
||||
'점수 관리',
|
||||
adminService.canManageScores,
|
||||
Icons.score,
|
||||
),
|
||||
_buildPermissionItem(
|
||||
'설정 관리',
|
||||
adminService.canManageSettings,
|
||||
Icons.settings,
|
||||
),
|
||||
_buildPermissionItem(
|
||||
'구독 관리',
|
||||
adminService.canManageSubscription,
|
||||
Icons.subscriptions,
|
||||
),
|
||||
_buildPermissionItem(
|
||||
'역할 관리',
|
||||
adminService.canManageRoles,
|
||||
Icons.admin_panel_settings,
|
||||
),
|
||||
_buildPermissionItem(
|
||||
'재무 정보 조회',
|
||||
adminService.canViewFinancials,
|
||||
Icons.attach_money,
|
||||
),
|
||||
_buildPermissionItem(
|
||||
'데이터 내보내기',
|
||||
adminService.canExportData,
|
||||
Icons.file_download,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 권한 항목 위젯
|
||||
Widget _buildPermissionItem(String title, bool hasPermission, IconData icon) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
icon,
|
||||
size: 18,
|
||||
color: hasPermission ? Colors.green : Colors.grey,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Text(title),
|
||||
const Spacer(),
|
||||
Icon(
|
||||
hasPermission ? Icons.check_circle : Icons.cancel,
|
||||
color: hasPermission ? Colors.green : Colors.red,
|
||||
size: 18,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 구독 정보 카드
|
||||
Widget _buildSubscriptionCard(SubscriptionService subscriptionService) {
|
||||
final subscription = subscriptionService.currentSubscription;
|
||||
|
||||
return Card(
|
||||
elevation: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.subscriptions),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'구독 정보',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
if (subscription != null) ...[
|
||||
_buildInfoRow('플랜', subscription.planName),
|
||||
_buildInfoRow('상태', _getStatusText(subscription.status)),
|
||||
_buildInfoRow('시작일', _formatDate(subscription.startDate)),
|
||||
_buildInfoRow('만료일', _formatDate(subscription.endDate)),
|
||||
_buildInfoRow('자동 갱신', subscription.autoRenew ? '활성화' : '비활성화'),
|
||||
_buildInfoRow('남은 일수', '${subscription.daysLeft}일'),
|
||||
] else
|
||||
const Text('활성화된 구독이 없습니다.'),
|
||||
const SizedBox(height: 16),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushNamed('/subscription/manage');
|
||||
},
|
||||
child: const Text('구독 관리'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 분석 데이터 카드
|
||||
Widget _buildAnalyticsCard() {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.analytics),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'클럽 통계',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
if (_analyticsData != null) ...[
|
||||
_buildInfoRow('총 회원 수', '${_analyticsData!['totalMembers'] ?? 0}명'),
|
||||
_buildInfoRow('활성 회원 수', '${_analyticsData!['activeMembers'] ?? 0}명'),
|
||||
_buildInfoRow('이번 달 신규 회원', '${_analyticsData!['newMembersThisMonth'] ?? 0}명'),
|
||||
_buildInfoRow('총 이벤트 수', '${_analyticsData!['totalEvents'] ?? 0}개'),
|
||||
_buildInfoRow('이번 달 이벤트', '${_analyticsData!['eventsThisMonth'] ?? 0}개'),
|
||||
_buildInfoRow('총 게임 수', '${_analyticsData!['totalGames'] ?? 0}게임'),
|
||||
_buildInfoRow('평균 점수', '${_analyticsData!['averageScore'] ?? 0}점'),
|
||||
] else
|
||||
const Text('통계 데이터를 불러올 수 없습니다.'),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 빠른 작업 카드
|
||||
Widget _buildQuickActionsCard(BuildContext context, AdminService adminService) {
|
||||
return Card(
|
||||
elevation: 2,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
const Icon(Icons.flash_on),
|
||||
const SizedBox(width: 8),
|
||||
Text(
|
||||
'빠른 작업',
|
||||
style: Theme.of(context).textTheme.titleLarge,
|
||||
),
|
||||
],
|
||||
),
|
||||
const Divider(),
|
||||
const SizedBox(height: 8),
|
||||
Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
children: [
|
||||
if (adminService.canManageMembers)
|
||||
_buildActionButton(
|
||||
context,
|
||||
'회원 관리',
|
||||
Icons.people,
|
||||
() => Navigator.of(context).pushNamed('/admin/members'),
|
||||
),
|
||||
if (adminService.canManageEvents)
|
||||
_buildActionButton(
|
||||
context,
|
||||
'이벤트 관리',
|
||||
Icons.event,
|
||||
() => Navigator.of(context).pushNamed('/admin/events'),
|
||||
),
|
||||
if (adminService.canManageSettings)
|
||||
_buildActionButton(
|
||||
context,
|
||||
'클럽 설정',
|
||||
Icons.settings,
|
||||
() => Navigator.of(context).pushNamed('/admin/settings'),
|
||||
),
|
||||
if (adminService.canManageRoles)
|
||||
_buildActionButton(
|
||||
context,
|
||||
'역할 관리',
|
||||
Icons.admin_panel_settings,
|
||||
() => Navigator.of(context).pushNamed('/admin/roles'),
|
||||
),
|
||||
if (adminService.canExportData)
|
||||
_buildActionButton(
|
||||
context,
|
||||
'데이터 내보내기',
|
||||
Icons.file_download,
|
||||
() => _showExportDialog(context),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 작업 버튼 위젯
|
||||
Widget _buildActionButton(
|
||||
BuildContext context,
|
||||
String label,
|
||||
IconData icon,
|
||||
VoidCallback onPressed,
|
||||
) {
|
||||
return ElevatedButton.icon(
|
||||
onPressed: onPressed,
|
||||
icon: Icon(icon, size: 18),
|
||||
label: Text(label),
|
||||
style: ElevatedButton.styleFrom(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 데이터 내보내기 다이얼로그
|
||||
void _showExportDialog(BuildContext context) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: const Text('데이터 내보내기'),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.people),
|
||||
title: const Text('회원 데이터'),
|
||||
onTap: () => _exportData(context, 'members'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.event),
|
||||
title: const Text('이벤트 데이터'),
|
||||
onTap: () => _exportData(context, 'events'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.score),
|
||||
title: const Text('점수 데이터'),
|
||||
onTap: () => _exportData(context, 'scores'),
|
||||
),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.analytics),
|
||||
title: const Text('통계 데이터'),
|
||||
onTap: () => _exportData(context, 'analytics'),
|
||||
),
|
||||
],
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () => Navigator.of(context).pop(),
|
||||
child: const Text('취소'),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 데이터 내보내기 처리
|
||||
Future<void> _exportData(BuildContext context, String dataType) async {
|
||||
Navigator.of(context).pop(); // 다이얼로그 닫기
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final adminService = Provider.of<AdminService>(context, listen: false);
|
||||
final downloadUrl = await adminService.exportClubData(dataType);
|
||||
|
||||
if (downloadUrl != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('데이터 내보내기 성공: $downloadUrl')),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('데이터 내보내기에 실패했습니다.')),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('데이터 내보내기 중 오류가 발생했습니다: $e')),
|
||||
);
|
||||
} finally {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// 정보 행 위젯
|
||||
Widget _buildInfoRow(String label, String value) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 4.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(
|
||||
'$label:',
|
||||
style: const TextStyle(fontWeight: FontWeight.bold),
|
||||
),
|
||||
Text(value),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 날짜 포맷팅
|
||||
String _formatDate(DateTime date) {
|
||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
/// 구독 상태 텍스트
|
||||
String _getStatusText(SubscriptionStatus status) {
|
||||
switch (status) {
|
||||
case SubscriptionStatus.active:
|
||||
return '활성화';
|
||||
case SubscriptionStatus.canceled:
|
||||
return '취소됨';
|
||||
case SubscriptionStatus.expired:
|
||||
return '만료됨';
|
||||
case SubscriptionStatus.pending:
|
||||
return '대기 중';
|
||||
case SubscriptionStatus.failed:
|
||||
return '실패';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user