Files
flutter-frame/lib/features/admin/presentation/screens/admin_home_screen.dart
2026-03-01 07:55:59 +09:00

239 lines
7.0 KiB
Dart

import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:go_router/go_router.dart';
import '../../../../core/router/route_names.dart';
import '../../../../core/utils/extensions.dart';
import '../../../../shared/widgets/chart_widgets/line_chart_widget.dart';
class AdminHomeScreen extends StatelessWidget {
const AdminHomeScreen({super.key});
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'관리자 대시보드',
style: context.textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const Gap(8),
Text(
'시스템 현황을 한눈에 확인하세요',
style: context.textTheme.bodyLarge?.copyWith(
color: context.colorScheme.onSurfaceVariant,
),
),
const Gap(24),
// 통계 카드
LayoutBuilder(
builder: (context, constraints) {
final crossAxisCount = context.isDesktop
? 4
: context.isTablet
? 2
: 1;
return GridView.count(
crossAxisCount: crossAxisCount,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
mainAxisSpacing: 16,
crossAxisSpacing: 16,
childAspectRatio: 1.8,
children: const [
_AdminStatCard(
title: '전체 사용자',
value: '1,234',
change: '+12%',
isPositive: true,
icon: Icons.people,
color: Colors.blue,
),
_AdminStatCard(
title: '활성 세션',
value: '89',
change: '+5%',
isPositive: true,
icon: Icons.devices,
color: Colors.green,
),
_AdminStatCard(
title: 'API 요청/분',
value: '2,456',
change: '-3%',
isPositive: false,
icon: Icons.api,
color: Colors.orange,
),
_AdminStatCard(
title: '시스템 오류',
value: '3',
change: '-50%',
isPositive: true,
icon: Icons.error_outline,
color: Colors.red,
),
],
);
},
),
const Gap(24),
// 차트 영역
SizedBox(
height: 300,
child: Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: LineChartWidget(
title: '주간 사용자 활동',
spots: const [
FlSpot(0, 30),
FlSpot(1, 45),
FlSpot(2, 38),
FlSpot(3, 60),
FlSpot(4, 55),
FlSpot(5, 70),
FlSpot(6, 65),
],
bottomTitles: (value, meta) => SideTitleWidget(
meta: meta,
child: Text(
['', '', '', '', '', '', '']
[value.toInt() % 7],
style: Theme.of(context).textTheme.bodySmall,
),
),
),
),
),
),
const Gap(24),
// 빠른 링크
Text(
'빠른 액세스',
style: context.textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
),
const Gap(16),
Wrap(
spacing: 12,
runSpacing: 12,
children: [
_QuickAction(
icon: Icons.people,
label: '사용자 관리',
onTap: () => context.goNamed(RouteNames.adminUsers),
),
_QuickAction(
icon: Icons.dashboard,
label: '대시보드',
onTap: () => context.goNamed(RouteNames.adminDashboard),
),
_QuickAction(
icon: Icons.settings,
label: '시스템 설정',
onTap: () => context.goNamed(RouteNames.adminSettings),
),
],
),
],
),
);
}
}
class _AdminStatCard extends StatelessWidget {
const _AdminStatCard({
required this.title,
required this.value,
required this.change,
required this.isPositive,
required this.icon,
required this.color,
});
final String title;
final String value;
final String change;
final bool isPositive;
final IconData icon;
final Color color;
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
),
Icon(icon, color: color, size: 24),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Text(
value,
style: Theme.of(context).textTheme.headlineMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
const Gap(8),
Text(
change,
style: Theme.of(context).textTheme.bodySmall?.copyWith(
color: isPositive ? Colors.green : Colors.red,
fontWeight: FontWeight.w600,
),
),
],
),
],
),
),
);
}
}
class _QuickAction extends StatelessWidget {
const _QuickAction({
required this.icon,
required this.label,
required this.onTap,
});
final IconData icon;
final String label;
final VoidCallback onTap;
@override
Widget build(BuildContext context) {
return ActionChip(
avatar: Icon(icon),
label: Text(label),
onPressed: onTap,
);
}
}