refactor: UI 화면 통합 및 불필요한 파일 정리
- 모든 *_redesign.dart 파일을 기본 화면 파일로 통합 - 백업용 컨트롤러 파일들 제거 (*_controller.backup.dart) - 사용하지 않는 예제 및 테스트 파일 제거 - Clean Architecture 적용 후 남은 정리 작업 완료 - 테스트 코드 정리 및 구조 개선 준비 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
875
lib/screens/overview/overview_screen.dart
Normal file
875
lib/screens/overview/overview_screen.dart
Normal file
@@ -0,0 +1,875 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:superport/screens/common/theme_shadcn.dart';
|
||||
import 'package:superport/screens/common/components/shadcn_components.dart';
|
||||
import 'package:superport/screens/overview/controllers/overview_controller.dart';
|
||||
// MockDataService 제거 - 실제 API 사용
|
||||
import 'package:superport/services/auth_service.dart';
|
||||
import 'package:superport/services/health_check_service.dart';
|
||||
import 'package:superport/core/widgets/auth_guard.dart';
|
||||
import 'package:superport/data/models/auth/auth_user.dart';
|
||||
|
||||
/// shadcn/ui 스타일로 재설계된 대시보드 화면
|
||||
class OverviewScreen extends StatefulWidget {
|
||||
const OverviewScreen({super.key});
|
||||
|
||||
@override
|
||||
State<OverviewScreen> createState() => _OverviewScreenState();
|
||||
}
|
||||
|
||||
class _OverviewScreenState extends State<OverviewScreen> {
|
||||
late final OverviewController _controller;
|
||||
late final HealthCheckService _healthCheckService;
|
||||
Map<String, dynamic>? _healthStatus;
|
||||
bool _isHealthCheckLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = OverviewController();
|
||||
_healthCheckService = HealthCheckService();
|
||||
_loadData();
|
||||
_checkHealthStatus();
|
||||
// 주기적인 헬스체크 시작 (30초마다)
|
||||
_healthCheckService.startPeriodicHealthCheck();
|
||||
}
|
||||
|
||||
Future<void> _loadData() async {
|
||||
await _controller.loadDashboardData();
|
||||
}
|
||||
|
||||
Future<void> _checkHealthStatus() async {
|
||||
setState(() {
|
||||
_isHealthCheckLoading = true;
|
||||
});
|
||||
|
||||
final result = await _healthCheckService.checkHealth();
|
||||
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_healthStatus = result;
|
||||
_isHealthCheckLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
_healthCheckService.stopPeriodicHealthCheck();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider.value(
|
||||
value: _controller,
|
||||
child: Consumer<OverviewController>(
|
||||
builder: (context, controller, child) {
|
||||
if (controller.isLoading) {
|
||||
return _buildLoadingState();
|
||||
}
|
||||
|
||||
if (controller.error != null) {
|
||||
return _buildErrorState(controller.error!);
|
||||
}
|
||||
|
||||
return Container(
|
||||
color: ShadcnTheme.background,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 라이선스 만료 알림 배너 (조건부 표시)
|
||||
if (controller.hasExpiringLicenses) ...[
|
||||
_buildLicenseExpiryBanner(controller),
|
||||
const SizedBox(height: 16),
|
||||
],
|
||||
|
||||
// 환영 섹션
|
||||
ShadcnCard(
|
||||
padding: const EdgeInsets.all(32),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FutureBuilder<AuthUser?>(
|
||||
future: context.read<AuthService>().getCurrentUser(),
|
||||
builder: (context, snapshot) {
|
||||
final userName = snapshot.data?.name ?? '사용자';
|
||||
return Text('안녕하세요, $userName님! 👋', style: ShadcnTheme.headingH3);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'오늘의 포트 운영 현황을 확인해보세요.',
|
||||
style: ShadcnTheme.bodyMuted,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Row(
|
||||
children: [
|
||||
ShadcnBadge(
|
||||
text: '실시간 모니터링',
|
||||
variant: ShadcnBadgeVariant.success,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
ShadcnBadge(
|
||||
text: '업데이트됨',
|
||||
variant: ShadcnBadgeVariant.outline,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// 통계 카드 그리드 (반응형)
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
final crossAxisCount =
|
||||
constraints.maxWidth > 1200
|
||||
? 4
|
||||
: constraints.maxWidth > 800
|
||||
? 2
|
||||
: 1;
|
||||
|
||||
return GridView.count(
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
crossAxisCount: crossAxisCount,
|
||||
crossAxisSpacing: 16,
|
||||
mainAxisSpacing: 16,
|
||||
childAspectRatio: 1.5,
|
||||
children: [
|
||||
_buildStatCard(
|
||||
'총 회사 수',
|
||||
'${_controller.totalCompanies}',
|
||||
Icons.business,
|
||||
ShadcnTheme.gradient1,
|
||||
),
|
||||
_buildStatCard(
|
||||
'총 사용자 수',
|
||||
'${_controller.totalUsers}',
|
||||
Icons.people,
|
||||
ShadcnTheme.gradient2,
|
||||
),
|
||||
_buildStatCard(
|
||||
'입고 장비',
|
||||
'${_controller.equipmentStatus?.available ?? 0}',
|
||||
Icons.inventory,
|
||||
ShadcnTheme.success,
|
||||
),
|
||||
_buildStatCard(
|
||||
'출고 장비',
|
||||
'${_controller.equipmentStatus?.inUse ?? 0}',
|
||||
Icons.local_shipping,
|
||||
ShadcnTheme.warning,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
const SizedBox(height: 32),
|
||||
|
||||
// 하단 콘텐츠
|
||||
LayoutBuilder(
|
||||
builder: (context, constraints) {
|
||||
if (constraints.maxWidth > 1000) {
|
||||
// 큰 화면: 가로로 배치
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(flex: 2, child: _buildLeftColumn()),
|
||||
const SizedBox(width: 24),
|
||||
Expanded(flex: 1, child: _buildRightColumn()),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
// 작은 화면: 세로로 배치
|
||||
return Column(
|
||||
children: [
|
||||
_buildLeftColumn(),
|
||||
const SizedBox(height: 24),
|
||||
_buildRightColumn(),
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoadingState() {
|
||||
return Container(
|
||||
color: ShadcnTheme.background,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
CircularProgressIndicator(color: ShadcnTheme.primary),
|
||||
const SizedBox(height: ShadcnTheme.spacing4),
|
||||
Text('대시보드를 불러오는 중...', style: ShadcnTheme.bodyMuted),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildErrorState(String error) {
|
||||
return Container(
|
||||
color: ShadcnTheme.background,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_outline,
|
||||
size: 48,
|
||||
color: ShadcnTheme.error,
|
||||
),
|
||||
const SizedBox(height: ShadcnTheme.spacing4),
|
||||
Text('오류가 발생했습니다', style: ShadcnTheme.headingH4),
|
||||
const SizedBox(height: ShadcnTheme.spacing2),
|
||||
Text(error, style: ShadcnTheme.bodyMuted),
|
||||
const SizedBox(height: ShadcnTheme.spacing4),
|
||||
ShadcnButton(
|
||||
text: '다시 시도',
|
||||
onPressed: _loadData,
|
||||
variant: ShadcnButtonVariant.primary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLeftColumn() {
|
||||
return Column(
|
||||
children: [
|
||||
// 차트 카드
|
||||
ShadcnCard(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('월별 활동 현황', style: ShadcnTheme.headingH4),
|
||||
Text('최근 6개월 데이터', style: ShadcnTheme.bodyMuted),
|
||||
],
|
||||
),
|
||||
ShadcnButton(
|
||||
text: '상세보기',
|
||||
onPressed: () {},
|
||||
variant: ShadcnButtonVariant.ghost,
|
||||
size: ShadcnButtonSize.small,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
Container(
|
||||
height: 200,
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.muted,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.analytics,
|
||||
size: 48,
|
||||
color: ShadcnTheme.mutedForeground,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text('차트 영역', style: ShadcnTheme.bodyMuted),
|
||||
Text(
|
||||
'fl_chart 라이브러리로 구현 예정',
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// 최근 활동
|
||||
ShadcnCard(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('최근 활동', style: ShadcnTheme.headingH4),
|
||||
ShadcnButton(
|
||||
text: '전체보기',
|
||||
onPressed: () {},
|
||||
variant: ShadcnButtonVariant.ghost,
|
||||
size: ShadcnButtonSize.small,
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Consumer<OverviewController>(
|
||||
builder: (context, controller, child) {
|
||||
final activities = controller.recentActivities;
|
||||
if (activities.isEmpty) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
child: Center(
|
||||
child: Text(
|
||||
'최근 활동이 없습니다',
|
||||
style: ShadcnTheme.bodyMuted,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return Column(
|
||||
children: activities.take(5).map((activity) =>
|
||||
_buildActivityItem(activity),
|
||||
).toList(),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRightColumn() {
|
||||
return FutureBuilder<AuthUser?>(
|
||||
future: context.read<AuthService>().getCurrentUser(),
|
||||
builder: (context, snapshot) {
|
||||
final userRole = snapshot.data?.role?.toLowerCase() ?? '';
|
||||
final isAdminOrManager = userRole == 'admin' || userRole == 'manager';
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
// 빠른 작업 (Admin과 Manager만 표시)
|
||||
if (isAdminOrManager) ...[
|
||||
ShadcnCard(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text('빠른 작업', style: ShadcnTheme.headingH4),
|
||||
const SizedBox(height: 16),
|
||||
_buildQuickActionButton(Icons.add_box, '장비 입고', '새 장비 등록'),
|
||||
const SizedBox(height: 12),
|
||||
_buildQuickActionButton(
|
||||
Icons.local_shipping,
|
||||
'장비 출고',
|
||||
'장비 대여 처리',
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_buildQuickActionButton(
|
||||
Icons.business_center,
|
||||
'회사 등록',
|
||||
'새 회사 추가',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
],
|
||||
|
||||
// 시스템 상태 (실시간 헬스체크)
|
||||
ShadcnCard(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text('시스템 상태', style: ShadcnTheme.headingH4),
|
||||
IconButton(
|
||||
icon: _isHealthCheckLoading
|
||||
? SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(ShadcnTheme.primary),
|
||||
),
|
||||
)
|
||||
: Icon(Icons.refresh, size: 20, color: ShadcnTheme.muted),
|
||||
onPressed: _isHealthCheckLoading ? null : _checkHealthStatus,
|
||||
tooltip: '새로고침',
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_buildHealthStatusItem('서버 상태', _getServerStatus()),
|
||||
_buildHealthStatusItem('데이터베이스', _getDatabaseStatus()),
|
||||
_buildHealthStatusItem('API 응답', _getApiResponseTime()),
|
||||
_buildHealthStatusItem('최종 체크', _getLastCheckTime()),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLicenseExpiryBanner(OverviewController controller) {
|
||||
final summary = controller.licenseExpirySummary;
|
||||
if (summary == null) return const SizedBox.shrink();
|
||||
|
||||
Color bannerColor = ShadcnTheme.warning;
|
||||
String bannerText = '';
|
||||
IconData bannerIcon = Icons.warning_amber_rounded;
|
||||
|
||||
if (summary.expired > 0) {
|
||||
bannerColor = ShadcnTheme.destructive;
|
||||
bannerText = '${summary.expired}개 라이선스 만료';
|
||||
bannerIcon = Icons.error_outline;
|
||||
} else if (summary.within30Days > 0) {
|
||||
bannerColor = ShadcnTheme.warning;
|
||||
bannerText = '${summary.within30Days}개 라이선스 30일 내 만료 예정';
|
||||
bannerIcon = Icons.warning_amber_rounded;
|
||||
} else if (summary.within60Days > 0) {
|
||||
bannerColor = ShadcnTheme.primary;
|
||||
bannerText = '${summary.within60Days}개 라이선스 60일 내 만료 예정';
|
||||
bannerIcon = Icons.info_outline;
|
||||
}
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: bannerColor.withValues(alpha: 0.1),
|
||||
border: Border.all(color: bannerColor.withValues(alpha: 0.3)),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(bannerIcon, color: bannerColor, size: 24),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'라이선스 관리 필요',
|
||||
style: ShadcnTheme.bodyMedium.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: bannerColor,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
bannerText,
|
||||
style: ShadcnTheme.bodySmall.copyWith(
|
||||
color: ShadcnTheme.foreground,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
// 라이선스 목록 페이지로 이동
|
||||
Navigator.pushNamed(context, '/licenses');
|
||||
},
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
'상세 보기',
|
||||
style: TextStyle(color: bannerColor),
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Icon(Icons.arrow_forward, color: bannerColor, size: 16),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatCard(
|
||||
String title,
|
||||
String value,
|
||||
IconData icon,
|
||||
Color color,
|
||||
) {
|
||||
return ShadcnCard(
|
||||
padding: const EdgeInsets.all(24),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 20),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.success.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.trending_up,
|
||||
size: 12,
|
||||
color: ShadcnTheme.success,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'+2.3%',
|
||||
style: ShadcnTheme.labelSmall.copyWith(
|
||||
color: ShadcnTheme.success,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(value, style: ShadcnTheme.headingH2),
|
||||
const SizedBox(height: 4),
|
||||
Text(title, style: ShadcnTheme.bodyMedium),
|
||||
Text('등록된 항목', style: ShadcnTheme.bodySmall),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildActivityItem(dynamic activity) {
|
||||
// 아이콘 매핑
|
||||
IconData getActivityIcon(String? type) {
|
||||
switch (type?.toLowerCase()) {
|
||||
case 'equipment_in':
|
||||
case '장비 입고':
|
||||
return Icons.inventory;
|
||||
case 'equipment_out':
|
||||
case '장비 출고':
|
||||
return Icons.local_shipping;
|
||||
case 'company':
|
||||
case '회사':
|
||||
return Icons.business;
|
||||
case 'user':
|
||||
case '사용자':
|
||||
return Icons.person_add;
|
||||
default:
|
||||
return Icons.settings;
|
||||
}
|
||||
}
|
||||
|
||||
// 색상 매핑
|
||||
Color getActivityColor(String? type) {
|
||||
switch (type?.toLowerCase()) {
|
||||
case 'equipment_in':
|
||||
case '장비 입고':
|
||||
return ShadcnTheme.success;
|
||||
case 'equipment_out':
|
||||
case '장비 출고':
|
||||
return ShadcnTheme.warning;
|
||||
case 'company':
|
||||
case '회사':
|
||||
return ShadcnTheme.info;
|
||||
case 'user':
|
||||
case '사용자':
|
||||
return ShadcnTheme.primary;
|
||||
default:
|
||||
return ShadcnTheme.mutedForeground;
|
||||
}
|
||||
}
|
||||
|
||||
final activityType = activity.activityType ?? '';
|
||||
final color = getActivityColor(activityType);
|
||||
final dateFormat = DateFormat('MM/dd HH:mm');
|
||||
final timestamp = activity.timestamp ?? DateTime.now();
|
||||
final entityName = activity.entityName ?? '이름 없음';
|
||||
final description = activity.description ?? '설명 없음';
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Icon(
|
||||
getActivityIcon(activityType),
|
||||
color: color,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
entityName,
|
||||
style: ShadcnTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
description,
|
||||
style: ShadcnTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Text(
|
||||
dateFormat.format(timestamp),
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildQuickActionButton(IconData icon, String title, String subtitle) {
|
||||
return GestureDetector(
|
||||
onTap: () {
|
||||
// 실제 기능 구현
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('$title 기능은 개발 중입니다.'),
|
||||
backgroundColor: ShadcnTheme.info,
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.black),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(icon, color: ShadcnTheme.primary),
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(title, style: ShadcnTheme.bodyMedium),
|
||||
Text(subtitle, style: ShadcnTheme.bodySmall),
|
||||
],
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
Icons.arrow_forward_ios,
|
||||
size: 16,
|
||||
color: ShadcnTheme.mutedForeground,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildStatusItem(String label, String status) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: ShadcnTheme.bodyMedium),
|
||||
ShadcnBadge(
|
||||
text: status,
|
||||
variant: ShadcnBadgeVariant.success,
|
||||
size: ShadcnBadgeSize.small,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 헬스 상태 아이템 빌더
|
||||
Widget _buildHealthStatusItem(String label, Map<String, dynamic> statusInfo) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Text(label, style: ShadcnTheme.bodyMedium),
|
||||
Row(
|
||||
children: [
|
||||
if (statusInfo['icon'] != null) ...[
|
||||
Icon(
|
||||
statusInfo['icon'] as IconData,
|
||||
size: 16,
|
||||
color: statusInfo['color'] as Color,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
],
|
||||
ShadcnBadge(
|
||||
text: statusInfo['text'] as String,
|
||||
variant: statusInfo['variant'] as ShadcnBadgeVariant,
|
||||
size: ShadcnBadgeSize.small,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 서버 상태 가져오기
|
||||
Map<String, dynamic> _getServerStatus() {
|
||||
if (_healthStatus == null) {
|
||||
return {
|
||||
'text': '확인 중',
|
||||
'variant': ShadcnBadgeVariant.secondary,
|
||||
'icon': Icons.pending,
|
||||
'color': ShadcnTheme.muted,
|
||||
};
|
||||
}
|
||||
|
||||
final isHealthy = _healthStatus!['success'] == true &&
|
||||
_healthStatus!['data']?['status'] == 'healthy';
|
||||
|
||||
return {
|
||||
'text': isHealthy ? '정상' : '오류',
|
||||
'variant': isHealthy ? ShadcnBadgeVariant.success : ShadcnBadgeVariant.destructive,
|
||||
'icon': isHealthy ? Icons.check_circle : Icons.error,
|
||||
'color': isHealthy ? ShadcnTheme.success : ShadcnTheme.destructive,
|
||||
};
|
||||
}
|
||||
|
||||
/// 데이터베이스 상태 가져오기
|
||||
Map<String, dynamic> _getDatabaseStatus() {
|
||||
if (_healthStatus == null) {
|
||||
return {
|
||||
'text': '확인 중',
|
||||
'variant': ShadcnBadgeVariant.secondary,
|
||||
'icon': Icons.pending,
|
||||
'color': ShadcnTheme.muted,
|
||||
};
|
||||
}
|
||||
|
||||
final dbStatus = _healthStatus!['data']?['database']?['status'] ?? 'unknown';
|
||||
final isConnected = dbStatus == 'connected';
|
||||
|
||||
return {
|
||||
'text': isConnected ? '연결됨' : '연결 안됨',
|
||||
'variant': isConnected ? ShadcnBadgeVariant.success : ShadcnBadgeVariant.warning,
|
||||
'icon': isConnected ? Icons.storage : Icons.cloud_off,
|
||||
'color': isConnected ? ShadcnTheme.success : ShadcnTheme.warning,
|
||||
};
|
||||
}
|
||||
|
||||
/// API 응답 시간 가져오기
|
||||
Map<String, dynamic> _getApiResponseTime() {
|
||||
if (_healthStatus == null) {
|
||||
return {
|
||||
'text': '측정 중',
|
||||
'variant': ShadcnBadgeVariant.secondary,
|
||||
'icon': Icons.timer,
|
||||
'color': ShadcnTheme.muted,
|
||||
};
|
||||
}
|
||||
|
||||
final responseTime = _healthStatus!['data']?['responseTime'] ?? 0;
|
||||
final timeMs = responseTime is num ? responseTime : 0;
|
||||
|
||||
ShadcnBadgeVariant variant;
|
||||
Color color;
|
||||
if (timeMs < 100) {
|
||||
variant = ShadcnBadgeVariant.success;
|
||||
color = ShadcnTheme.success;
|
||||
} else if (timeMs < 500) {
|
||||
variant = ShadcnBadgeVariant.warning;
|
||||
color = ShadcnTheme.warning;
|
||||
} else {
|
||||
variant = ShadcnBadgeVariant.destructive;
|
||||
color = ShadcnTheme.destructive;
|
||||
}
|
||||
|
||||
return {
|
||||
'text': '${timeMs}ms',
|
||||
'variant': variant,
|
||||
'icon': Icons.speed,
|
||||
'color': color,
|
||||
};
|
||||
}
|
||||
|
||||
/// 마지막 체크 시간 가져오기
|
||||
Map<String, dynamic> _getLastCheckTime() {
|
||||
if (_healthStatus == null) {
|
||||
return {
|
||||
'text': '없음',
|
||||
'variant': ShadcnBadgeVariant.secondary,
|
||||
'icon': Icons.access_time,
|
||||
'color': ShadcnTheme.muted,
|
||||
};
|
||||
}
|
||||
|
||||
final timestamp = _healthStatus!['data']?['timestamp'];
|
||||
if (timestamp != null) {
|
||||
try {
|
||||
final date = DateTime.parse(timestamp);
|
||||
final formatter = DateFormat('HH:mm:ss');
|
||||
return {
|
||||
'text': formatter.format(date),
|
||||
'variant': ShadcnBadgeVariant.outline,
|
||||
'icon': Icons.access_time,
|
||||
'color': ShadcnTheme.foreground,
|
||||
};
|
||||
} catch (e) {
|
||||
// 파싱 실패
|
||||
}
|
||||
}
|
||||
|
||||
// 현재 시간 사용
|
||||
final now = DateTime.now();
|
||||
final formatter = DateFormat('HH:mm:ss');
|
||||
return {
|
||||
'text': formatter.format(now),
|
||||
'variant': ShadcnBadgeVariant.outline,
|
||||
'icon': Icons.access_time,
|
||||
'color': ShadcnTheme.foreground,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user