import 'package:flutter/material.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:superport/data/models/maintenance_stats_dto.dart'; import 'package:superport/screens/common/theme_shadcn.dart'; /// 유지보수 대시보드 상태 요약 카드 /// 60일내, 30일내, 7일내, 만료된 계약 통계를 표시합니다. class StatusSummaryCards extends StatelessWidget { final MaintenanceStatsDto stats; final bool isLoading; final String? error; final VoidCallback? onRetry; final Function(String)? onCardTap; // 카드 탭 시 호출 (카드 타입 전달) const StatusSummaryCards({ super.key, required this.stats, this.isLoading = false, this.error, this.onRetry, this.onCardTap, }); @override Widget build(BuildContext context) { // 로딩 상태 if (isLoading) { return _buildLoadingCards(); } // 에러 상태 if (error != null) { return _buildErrorCard(); } // 정상 상태 - 4개 카드 표시 return _buildNormalCards(); } /// 로딩 상태 카드들 Widget _buildLoadingCards() { return Row( children: List.generate(4, (index) => Expanded( child: Container( margin: EdgeInsets.only(right: index < 3 ? 16 : 0), child: ShadCard( child: const Padding( padding: EdgeInsets.all(20), child: Center( child: SizedBox( width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2), ), ), ), ), ), )), ); } /// 에러 상태 카드 Widget _buildErrorCard() { return ShadCard( child: Padding( padding: const EdgeInsets.all(24), child: Column( children: [ Icon( Icons.error_outline, size: 48, color: ShadcnTheme.destructive, ), const SizedBox(height: 16), Text( '통계 로딩 실패', style: ShadcnTheme.headingH3.copyWith( color: ShadcnTheme.destructive, ), ), const SizedBox(height: 8), Text( error ?? '알 수 없는 오류가 발생했습니다', style: ShadcnTheme.bodyLarge.copyWith( color: ShadcnTheme.mutedForeground, ), textAlign: TextAlign.center, ), const SizedBox(height: 16), if (onRetry != null) ShadButton( onPressed: onRetry, child: const Text('다시 시도'), ), ], ), ), ); } /// 정상 상태 카드들 Widget _buildNormalCards() { final cardData = [ _CardData( title: '60일 내', count: stats.expiring60Days, subtitle: '만료 예정', icon: Icons.schedule_outlined, color: _getStatusColor(MaintenanceCardStatus.warning), status: stats.expiring60Days > 0 ? MaintenanceCardStatus.warning : MaintenanceCardStatus.active, actionLabel: '계획하기', cardType: 'expiring_60', ), _CardData( title: '30일 내', count: stats.expiring30Days, subtitle: '만료 예정', icon: Icons.warning_amber_outlined, color: _getStatusColor(MaintenanceCardStatus.urgent), status: stats.expiring30Days > 0 ? MaintenanceCardStatus.urgent : MaintenanceCardStatus.active, actionLabel: '예약하기', cardType: 'expiring_30', ), _CardData( title: '7일 내', count: stats.expiring7Days, subtitle: '만료 임박', icon: Icons.priority_high_outlined, color: _getStatusColor(MaintenanceCardStatus.critical), status: stats.expiring7Days > 0 ? MaintenanceCardStatus.critical : MaintenanceCardStatus.active, actionLabel: '즉시 처리', cardType: 'expiring_7', ), _CardData( title: '만료됨', count: stats.expiredContracts, subtitle: '조치 필요', icon: Icons.error_outline, color: _getStatusColor(MaintenanceCardStatus.expired), status: stats.expiredContracts > 0 ? MaintenanceCardStatus.expired : MaintenanceCardStatus.active, actionLabel: '갱신하기', cardType: 'expired', ), ]; return Row( children: cardData.asMap().entries.map((entry) { int index = entry.key; _CardData card = entry.value; return Expanded( child: Container( margin: EdgeInsets.only(right: index < 3 ? 16 : 0), child: _buildMaintenanceCard(card), ), ); }).toList(), ); } /// 단일 유지보수 카드 빌더 Widget _buildMaintenanceCard(_CardData cardData) { return ShadCard( child: InkWell( onTap: () => onCardTap?.call(cardData.cardType), borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // 헤더 (아이콘 + 상태 인디케이터) Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Icon( cardData.icon, size: 28, color: cardData.color, ), _buildStatusIndicator(cardData.status), ], ), const SizedBox(height: 16), // 제목 Text( cardData.title, style: ShadcnTheme.bodyLarge.copyWith( color: ShadcnTheme.mutedForeground, fontWeight: FontWeight.w500, ), ), const SizedBox(height: 4), // 개수 (메인 메트릭) Text( cardData.count.toString(), style: ShadcnTheme.headingH1.copyWith( color: cardData.color, fontWeight: FontWeight.bold, fontSize: 32, ), ), const SizedBox(height: 4), // 부제목 Text( cardData.subtitle, style: ShadcnTheme.bodyMedium.copyWith( color: ShadcnTheme.mutedForeground, ), ), const SizedBox(height: 16), // 액션 버튼 (조건부 표시) if (cardData.count > 0 && cardData.actionLabel != null) SizedBox( width: double.infinity, child: ShadButton.outline( onPressed: () => onCardTap?.call(cardData.cardType), size: ShadButtonSize.sm, child: Text( cardData.actionLabel!, style: TextStyle( fontSize: 12, color: cardData.color, ), ), ), ), ], ), ), ), ); } /// 상태 인디케이터 Widget _buildStatusIndicator(MaintenanceCardStatus status) { Color color; IconData icon; switch (status) { case MaintenanceCardStatus.critical: color = Colors.red; icon = Icons.circle; break; case MaintenanceCardStatus.urgent: color = Colors.orange; icon = Icons.circle; break; case MaintenanceCardStatus.warning: color = Colors.amber; icon = Icons.circle; break; case MaintenanceCardStatus.expired: color = Colors.red.shade800; icon = Icons.circle; break; case MaintenanceCardStatus.active: color = Colors.green; icon = Icons.circle; break; } return Container( width: 12, height: 12, decoration: BoxDecoration( color: color, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: color.withValues(alpha: 0.3), blurRadius: 4, spreadRadius: 1, ), ], ), ); } /// 상태별 색상 반환 Color _getStatusColor(MaintenanceCardStatus status) { switch (status) { case MaintenanceCardStatus.critical: return Colors.red.shade600; case MaintenanceCardStatus.urgent: return Colors.orange.shade600; case MaintenanceCardStatus.warning: return Colors.amber.shade600; case MaintenanceCardStatus.expired: return Colors.red.shade800; case MaintenanceCardStatus.active: return Colors.green.shade600; } } } /// 모바일 대응 스택 레이아웃 (세로 카드 배치) class StatusSummaryCardsStack extends StatelessWidget { final MaintenanceStatsDto stats; final bool isLoading; final String? error; final VoidCallback? onRetry; final Function(String)? onCardTap; const StatusSummaryCardsStack({ super.key, required this.stats, this.isLoading = false, this.error, this.onRetry, this.onCardTap, }); @override Widget build(BuildContext context) { // 모바일에서는 2x2 그리드로 표시 return Column( children: [ Row( children: [ Expanded( child: StatusSummaryCards( stats: MaintenanceStatsDto(expiring60Days: stats.expiring60Days), isLoading: isLoading, error: error, onRetry: onRetry, onCardTap: onCardTap, ), ), const SizedBox(width: 16), Expanded( child: StatusSummaryCards( stats: MaintenanceStatsDto(expiring30Days: stats.expiring30Days), isLoading: isLoading, error: error, onRetry: onRetry, onCardTap: onCardTap, ), ), ], ), const SizedBox(height: 16), Row( children: [ Expanded( child: StatusSummaryCards( stats: MaintenanceStatsDto(expiring7Days: stats.expiring7Days), isLoading: isLoading, error: error, onRetry: onRetry, onCardTap: onCardTap, ), ), const SizedBox(width: 16), Expanded( child: StatusSummaryCards( stats: MaintenanceStatsDto(expiredContracts: stats.expiredContracts), isLoading: isLoading, error: error, onRetry: onRetry, onCardTap: onCardTap, ), ), ], ), ], ); } } /// 카드 데이터 모델 (내부 사용) class _CardData { final String title; final int count; final String subtitle; final IconData icon; final Color color; final MaintenanceCardStatus status; final String? actionLabel; final String cardType; const _CardData({ required this.title, required this.count, required this.subtitle, required this.icon, required this.color, required this.status, this.actionLabel, required this.cardType, }); }