457 lines
14 KiB
Dart
457 lines
14 KiB
Dart
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 '실패';
|
|
}
|
|
}
|
|
}
|