프로젝트 최초 커밋

This commit is contained in:
JiWoong Sul
2025-07-02 17:45:44 +09:00
commit e346f83c97
235 changed files with 23139 additions and 0 deletions

View 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,
},
];
}
}

View 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),
],
),
);
}
}

View 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(),
);
}
}

View 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,
),
],
);
}
}