프로젝트 최초 커밋
This commit is contained in:
60
lib/screens/overview/controllers/overview_controller.dart
Normal file
60
lib/screens/overview/controllers/overview_controller.dart
Normal file
@@ -0,0 +1,60 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
import 'package:superport/screens/common/theme_tailwind.dart';
|
||||
|
||||
// 대시보드(Overview) 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러
|
||||
class OverviewController {
|
||||
final MockDataService dataService;
|
||||
|
||||
int totalCompanies = 0;
|
||||
int totalUsers = 0;
|
||||
int totalEquipmentIn = 0;
|
||||
int totalEquipmentOut = 0;
|
||||
int totalLicenses = 0;
|
||||
|
||||
// 최근 활동 데이터
|
||||
List<Map<String, dynamic>> recentActivities = [];
|
||||
|
||||
OverviewController({required this.dataService});
|
||||
|
||||
// 데이터 로드 및 통계 계산
|
||||
void loadData() {
|
||||
totalCompanies = dataService.getAllCompanies().length;
|
||||
totalUsers = dataService.getAllUsers().length;
|
||||
// 실제 서비스에서는 아래 메서드 구현 필요
|
||||
totalEquipmentIn = 32; // 임시 데이터
|
||||
totalEquipmentOut = 18; // 임시 데이터
|
||||
totalLicenses = dataService.getAllLicenses().length;
|
||||
_loadRecentActivities();
|
||||
}
|
||||
|
||||
// 최근 활동 데이터 로드 (임시 데이터)
|
||||
void _loadRecentActivities() {
|
||||
recentActivities = [
|
||||
{
|
||||
'type': '장비 입고',
|
||||
'title': '라우터 입고 처리 완료',
|
||||
'time': '30분 전',
|
||||
'user': '홍길동',
|
||||
'icon': Icons.input,
|
||||
'color': AppThemeTailwind.success,
|
||||
},
|
||||
{
|
||||
'type': '사용자 추가',
|
||||
'title': '새 관리자 등록',
|
||||
'time': '1시간 전',
|
||||
'user': '김철수',
|
||||
'icon': Icons.person_add,
|
||||
'color': AppThemeTailwind.primary,
|
||||
},
|
||||
{
|
||||
'type': '장비 출고',
|
||||
'title': '모니터 5대 출고 처리',
|
||||
'time': '2시간 전',
|
||||
'user': '이영희',
|
||||
'icon': Icons.output,
|
||||
'color': AppThemeTailwind.warning,
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
206
lib/screens/overview/overview_screen.dart
Normal file
206
lib/screens/overview/overview_screen.dart
Normal file
@@ -0,0 +1,206 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport/screens/common/layout_components.dart';
|
||||
import 'package:superport/screens/common/main_layout.dart';
|
||||
import 'package:superport/screens/common/theme_tailwind.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
import 'package:superport/screens/overview/controllers/overview_controller.dart';
|
||||
import 'package:superport/screens/overview/widgets/stats_grid.dart';
|
||||
import 'package:superport/screens/overview/widgets/recent_activities_list.dart';
|
||||
|
||||
// 대시보드(Overview) 화면 (UI만 담당, 상태/로직/위젯 분리)
|
||||
class OverviewScreen extends StatefulWidget {
|
||||
const OverviewScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
_OverviewScreenState createState() => _OverviewScreenState();
|
||||
}
|
||||
|
||||
class _OverviewScreenState extends State<OverviewScreen> {
|
||||
late final OverviewController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = OverviewController(dataService: MockDataService());
|
||||
_controller.loadData();
|
||||
}
|
||||
|
||||
void _reload() {
|
||||
setState(() {
|
||||
_controller.loadData();
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// 전체 배경색을 회색(AppThemeTailwind.surface)으로 지정
|
||||
return Container(
|
||||
color: AppThemeTailwind.surface, // 회색 배경
|
||||
child: MainLayout(
|
||||
title: '', // 타이틀 없음
|
||||
currentRoute: Routes.home,
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onPressed: _reload,
|
||||
color: AppThemeTailwind.muted,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.notifications_none),
|
||||
onPressed: () {},
|
||||
color: AppThemeTailwind.muted,
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.logout),
|
||||
tooltip: '로그아웃',
|
||||
onPressed: () {
|
||||
Navigator.of(context).pushReplacementNamed('/login');
|
||||
},
|
||||
color: AppThemeTailwind.muted,
|
||||
),
|
||||
],
|
||||
child: SingleChildScrollView(
|
||||
padding: EdgeInsets.zero, // 여백 0
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 상단 경로 표기 완전 삭제
|
||||
// 하단부 전체를 감싸는 라운드 흰색 박스
|
||||
Container(
|
||||
margin: const EdgeInsets.all(4), // 외부 여백만 적용
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.white, // 흰색 배경
|
||||
borderRadius: BorderRadius.circular(24), // 라운드 처리
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withOpacity(0.04),
|
||||
blurRadius: 12,
|
||||
offset: const Offset(0, 4),
|
||||
),
|
||||
],
|
||||
),
|
||||
padding: const EdgeInsets.all(32), // 내부 여백 유지
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 통계 카드 그리드
|
||||
Container(
|
||||
margin: const EdgeInsets.only(bottom: 32),
|
||||
child: StatsGrid(
|
||||
totalCompanies: _controller.totalCompanies,
|
||||
totalUsers: _controller.totalUsers,
|
||||
totalLicenses: _controller.totalLicenses,
|
||||
totalEquipmentIn: _controller.totalEquipmentIn,
|
||||
totalEquipmentOut: _controller.totalEquipmentOut,
|
||||
),
|
||||
),
|
||||
_buildActivitySection(),
|
||||
const SizedBox(height: 32),
|
||||
_buildRecentItemsSection(),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActivitySection() {
|
||||
// MetronicCard로 감싸고, 섹션 헤더 스타일 통일
|
||||
return MetronicCard(
|
||||
title: '시스템 활동',
|
||||
margin: const EdgeInsets.only(bottom: 32),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildActivityChart(),
|
||||
const SizedBox(height: 20),
|
||||
const Divider(color: Color(0xFFF3F6F9)),
|
||||
const SizedBox(height: 20),
|
||||
_buildActivityLegend(),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActivityChart() {
|
||||
// Metronic 스타일: 카드 내부 차트 영역, 라운드, 밝은 배경, 컬러 강조
|
||||
return Container(
|
||||
height: 200,
|
||||
padding: const EdgeInsets.all(24),
|
||||
decoration: BoxDecoration(
|
||||
color: AppThemeTailwind.light,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Icon(
|
||||
Icons.bar_chart,
|
||||
size: 56,
|
||||
color: AppThemeTailwind.primary,
|
||||
),
|
||||
const SizedBox(height: 18),
|
||||
Text('월별 장비 입/출고 추이', style: AppThemeTailwind.subheadingStyle),
|
||||
const SizedBox(height: 10),
|
||||
Text(
|
||||
'실제 구현 시 차트 라이브러리 (fl_chart 등) 사용',
|
||||
style: AppThemeTailwind.smallText,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActivityLegend() {
|
||||
// Metronic 스타일: 라운드, 컬러, 폰트 통일
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
_buildLegendItem('장비 입고', AppThemeTailwind.success),
|
||||
const SizedBox(width: 32),
|
||||
_buildLegendItem('장비 출고', AppThemeTailwind.warning),
|
||||
const SizedBox(width: 32),
|
||||
_buildLegendItem('라이센스 등록', AppThemeTailwind.info),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLegendItem(String text, Color color) {
|
||||
// Metronic 스타일: 컬러 원, 텍스트, 여백
|
||||
return Row(
|
||||
children: [
|
||||
Container(
|
||||
width: 14,
|
||||
height: 14,
|
||||
decoration: BoxDecoration(color: color, shape: BoxShape.circle),
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Text(
|
||||
text,
|
||||
style: AppThemeTailwind.smallText.copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppThemeTailwind.dark,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRecentItemsSection() {
|
||||
// Metronic 스타일: 카드, 섹션 헤더, 리스트 여백/컬러 통일
|
||||
return MetronicCard(
|
||||
title: '최근 활동',
|
||||
child: Column(
|
||||
children: [
|
||||
const Divider(indent: 0, endIndent: 0, color: Color(0xFFF3F6F9)),
|
||||
const SizedBox(height: 16),
|
||||
RecentActivitiesList(recentActivities: _controller.recentActivities),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
56
lib/screens/overview/widgets/recent_activities_list.dart
Normal file
56
lib/screens/overview/widgets/recent_activities_list.dart
Normal file
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport/screens/common/theme_tailwind.dart';
|
||||
|
||||
// 최근 활동 리스트 위젯 (SRP, 재사용성)
|
||||
class RecentActivitiesList extends StatelessWidget {
|
||||
final List<Map<String, dynamic>> recentActivities;
|
||||
const RecentActivitiesList({super.key, required this.recentActivities});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
children:
|
||||
recentActivities.map((activity) {
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: activity['color'] as Color,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: Icon(
|
||||
activity['icon'] as IconData,
|
||||
color: Colors.white,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
title: Text(
|
||||
activity['title'] as String,
|
||||
style: AppThemeTailwind.subheadingStyle,
|
||||
),
|
||||
subtitle: Text(
|
||||
'${activity['type']} • ${activity['user']}',
|
||||
style: AppThemeTailwind.smallText,
|
||||
),
|
||||
trailing: Text(
|
||||
activity['time'] as String,
|
||||
style: AppThemeTailwind.smallText.copyWith(
|
||||
color: AppThemeTailwind.muted,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (activity != recentActivities.last)
|
||||
const Divider(
|
||||
height: 1,
|
||||
indent: 68,
|
||||
endIndent: 16,
|
||||
color: (Color(0xFFEEEEF2)),
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
}
|
||||
83
lib/screens/overview/widgets/stats_grid.dart
Normal file
83
lib/screens/overview/widgets/stats_grid.dart
Normal file
@@ -0,0 +1,83 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport/screens/common/theme_tailwind.dart';
|
||||
import 'package:superport/screens/common/layout_components.dart';
|
||||
|
||||
// 대시보드 통계 카드 그리드 위젯 (SRP, 재사용성)
|
||||
class StatsGrid extends StatelessWidget {
|
||||
final int totalCompanies;
|
||||
final int totalUsers;
|
||||
final int totalLicenses;
|
||||
final int totalEquipmentIn;
|
||||
final int totalEquipmentOut;
|
||||
|
||||
const StatsGrid({
|
||||
super.key,
|
||||
required this.totalCompanies,
|
||||
required this.totalUsers,
|
||||
required this.totalLicenses,
|
||||
required this.totalEquipmentIn,
|
||||
required this.totalEquipmentOut,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GridView.count(
|
||||
crossAxisCount: 3,
|
||||
crossAxisSpacing: 16,
|
||||
mainAxisSpacing: 16,
|
||||
shrinkWrap: true,
|
||||
childAspectRatio: 2.5,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
children: [
|
||||
MetronicStatsCard(
|
||||
title: '등록된 회사',
|
||||
value: '$totalCompanies',
|
||||
icon: Icons.business,
|
||||
iconBackgroundColor: AppThemeTailwind.info,
|
||||
showTrend: true,
|
||||
trendPercentage: 2.5,
|
||||
isPositiveTrend: true,
|
||||
),
|
||||
MetronicStatsCard(
|
||||
title: '등록된 사용자',
|
||||
value: '$totalUsers',
|
||||
icon: Icons.person,
|
||||
iconBackgroundColor: AppThemeTailwind.primary,
|
||||
showTrend: true,
|
||||
trendPercentage: 3.7,
|
||||
isPositiveTrend: true,
|
||||
),
|
||||
MetronicStatsCard(
|
||||
title: '유효 라이센스',
|
||||
value: '$totalLicenses',
|
||||
icon: Icons.vpn_key,
|
||||
iconBackgroundColor: AppThemeTailwind.secondary,
|
||||
),
|
||||
MetronicStatsCard(
|
||||
title: '총 장비 입고',
|
||||
value: '$totalEquipmentIn',
|
||||
icon: Icons.input,
|
||||
iconBackgroundColor: AppThemeTailwind.success,
|
||||
showTrend: true,
|
||||
trendPercentage: 1.8,
|
||||
isPositiveTrend: true,
|
||||
),
|
||||
MetronicStatsCard(
|
||||
title: '총 장비 출고',
|
||||
value: '$totalEquipmentOut',
|
||||
icon: Icons.output,
|
||||
iconBackgroundColor: AppThemeTailwind.warning,
|
||||
),
|
||||
MetronicStatsCard(
|
||||
title: '현재 재고',
|
||||
value: '${totalEquipmentIn - totalEquipmentOut}',
|
||||
icon: Icons.inventory_2,
|
||||
iconBackgroundColor: AppThemeTailwind.danger,
|
||||
showTrend: true,
|
||||
trendPercentage: 0.7,
|
||||
isPositiveTrend: false,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user