import 'package:flutter/material.dart'; import 'package:google_fonts/google_fonts.dart'; /// ERP 시스템에 최적화된 색채 심리학 기반 테마 시스템 class ShadcnTheme { // ============= 기본 색상 팔레트 ============= // 배경 및 표면 색상 static const Color background = Color(0xFFFFFFFF); static const Color backgroundSecondary = Color(0xFFF9FAFB); // 보조 배경 static const Color surface = Color(0xFFFFFFFF); static const Color surfaceHover = Color(0xFFF3F4F6); // 호버 상태 // 텍스트 색상 static const Color foreground = Color(0xFF111827); // 주요 텍스트 (진한 검정) static const Color foregroundSecondary = Color(0xFF374151); // 보조 텍스트 static const Color foregroundMuted = Color(0xFF6B7280); // 비활성 텍스트 static const Color foregroundSubtle = Color(0xFF9CA3AF); // 희미한 텍스트 // Primary 색상 (신뢰감 있는 블루) static const Color primary = Color(0xFF2563EB); // blue-600 static const Color primaryDark = Color(0xFF1E40AF); // blue-800 static const Color primaryLight = Color(0xFFDBEAFE); // blue-100 static const Color primaryForeground = Color(0xFFFFFFFF); // Secondary 색상 (중립 그레이) static const Color secondary = Color(0xFF6B7280); // gray-500 static const Color secondaryDark = Color(0xFF374151); // gray-700 static const Color secondaryLight = Color(0xFFF9FAFB); // gray-50 static const Color secondaryForeground = Color(0xFF111827); // ============= 시맨틱 색상 ============= static const Color success = Color(0xFF059669); // emerald-600 static const Color successLight = Color(0xFFD1FAE5); // emerald-100 static const Color successForeground = Color(0xFFFFFFFF); static const Color warning = Color(0xFFD97706); // amber-600 static const Color warningLight = Color(0xFFFEF3C7); // amber-100 static const Color warningForeground = Color(0xFFFFFFFF); static const Color error = Color(0xFFDC2626); // red-600 static const Color errorLight = Color(0xFFFEE2E2); // red-100 static const Color errorForeground = Color(0xFFFFFFFF); static const Color info = Color(0xFF0891B2); // cyan-600 static const Color infoLight = Color(0xFFCFFAFE); // cyan-100 static const Color infoForeground = Color(0xFFFFFFFF); // ============= 비즈니스 상태 색상 (색체심리학 기반) ============= // 회사 구분 색상 - Phase 10 업데이트 static const Color companyHeadquarters = Color(0xFF2563EB); // 본사 - 진한 파랑 (권위, 안정성) static const Color companyBranch = Color(0xFF3B82F6); // 지점 - 밝은 파랑 (연결성, 확장) static const Color companyPartner = Color(0xFF10B981); // 파트너사 - 에메랄드 (협력, 신뢰) static const Color companyCustomer = Color(0xFF059669); // 고객사 - 진한 그린 (성장, 번영) // 트랜잭션 상태 색상 - Phase 10 업데이트 static const Color equipmentIn = Color(0xFF10B981); // 입고 - 에메랄드 (추가/성장) static const Color equipmentOut = Color(0xFF3B82F6); // 출고 - 블루 (이동/처리) static const Color equipmentRent = Color(0xFF8B5CF6); // 대여 - Purple (임시 상태) static const Color equipmentDisposal = Color(0xFF6B7280); // 폐기 - Gray (종료/비활성) static const Color equipmentRepair = Color(0xFFF59E0B); // 수리중 - Amber (주의/진행) static const Color equipmentUnknown = Color(0xFF9CA3AF); // 알수없음 - Light Gray // ============= UI 요소 색상 ============= static const Color border = Color(0xFFE5E7EB); // gray-200 static const Color borderStrong = Color(0xFFD1D5DB); // gray-300 static const Color borderFocus = Color(0xFF2563EB); // primary static const Color divider = Color(0xFFF3F4F6); // gray-100 static const Color card = Color(0xFFFFFFFF); static const Color cardForeground = Color(0xFF111827); static const Color cardHover = Color(0xFFF9FAFB); static const Color input = Color(0xFFFFFFFF); static const Color inputBorder = Color(0xFFD1D5DB); // gray-300 static const Color inputHover = Color(0xFFF9FAFB); static const Color inputFocus = Color(0xFF2563EB); // 기존 호환성을 위한 별칭 static const Color destructive = error; static const Color destructiveForeground = errorForeground; static const Color muted = backgroundSecondary; static const Color mutedForeground = foregroundMuted; static const Color accent = primary; static const Color accentForeground = primaryForeground; static const Color ring = primaryDark; static const Color popover = card; static const Color popoverForeground = cardForeground; // Teal 그라데이션 색상 (기존 호환) static const Color gradient1 = primary; static const Color gradient2 = primaryDark; static const Color gradient3 = Color(0xFF1D4ED8); // blue-700 // 추가 색상 (기존 호환) static const Color blue = primary; static const Color purple = equipmentRent; static const Color green = equipmentIn; // Phase 10 - 알림/경고 색상 체계 (긴급도 기반) static const Color alertNormal = Color(0xFF10B981); // 60일 이상 - 안전 (그린) static const Color alertWarning60 = Color(0xFFF59E0B); // 60일 이내 - 주의 (앰버) static const Color alertWarning30 = Color(0xFFF97316); // 30일 이내 - 경고 (오렌지) static const Color alertCritical7 = Color(0xFFEF4444); // 7일 이내 - 위험 (레드) static const Color alertExpired = Color(0xFFDC2626); // 만료됨 - 심각 (진한 레드) static const Color radius = Color(0xFF000000); // 사용하지 않음 // ============= 그림자 시스템 ============= static List get shadowXs => [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 2, offset: const Offset(0, 1), ), ]; static List get shadowSm => [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 4, offset: const Offset(0, 2), ), ]; static List get shadowMd => [ BoxShadow( color: Colors.black.withValues(alpha: 0.08), blurRadius: 8, offset: const Offset(0, 4), ), ]; static List get shadowLg => [ BoxShadow( color: Colors.black.withValues(alpha: 0.10), blurRadius: 16, offset: const Offset(0, 8), ), ]; static List get shadowXl => [ BoxShadow( color: Colors.black.withValues(alpha: 0.15), blurRadius: 24, offset: const Offset(0, 12), ), ]; // 카드 및 버튼 그림자 (기존 호환) static List get cardShadow => shadowMd; static List get buttonShadow => shadowSm; // ============= 간격 시스템 (8px 기반) ============= static const double spacing0 = 0.0; static const double spacing1 = 4.0; static const double spacing2 = 8.0; static const double spacing3 = 12.0; static const double spacing4 = 16.0; static const double spacing5 = 20.0; static const double spacing6 = 24.0; static const double spacing7 = 28.0; static const double spacing8 = 32.0; static const double spacing9 = 36.0; static const double spacing10 = 40.0; static const double spacing12 = 48.0; static const double spacing14 = 56.0; static const double spacing16 = 64.0; static const double spacing20 = 80.0; static const double spacing24 = 96.0; // ============= 라운드 설정 ============= static const double radiusNone = 0.0; static const double radiusXs = 2.0; static const double radiusSm = 4.0; static const double radiusMd = 6.0; static const double radiusLg = 8.0; static const double radiusXl = 12.0; static const double radius2xl = 16.0; static const double radius3xl = 24.0; static const double radiusFull = 9999.0; // ============= 타이포그래피 시스템 ============= // 헤딩 스타일 static TextStyle get headingH1 => GoogleFonts.inter( fontSize: 36, fontWeight: FontWeight.w700, color: foreground, letterSpacing: -0.02, height: 1.2, ); static TextStyle get headingH2 => GoogleFonts.inter( fontSize: 28, fontWeight: FontWeight.w600, color: foreground, letterSpacing: -0.01, height: 1.3, ); static TextStyle get headingH3 => GoogleFonts.inter( fontSize: 24, fontWeight: FontWeight.w600, color: foreground, letterSpacing: -0.01, height: 1.35, ); static TextStyle get headingH4 => GoogleFonts.inter( fontSize: 20, fontWeight: FontWeight.w500, color: foreground, letterSpacing: 0, height: 1.4, ); static TextStyle get headingH5 => GoogleFonts.inter( fontSize: 18, fontWeight: FontWeight.w500, color: foreground, letterSpacing: 0, height: 1.4, ); static TextStyle get headingH6 => GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w500, color: foreground, letterSpacing: 0, height: 1.5, ); // 본문 스타일 static TextStyle get bodyLarge => GoogleFonts.inter( fontSize: 16, fontWeight: FontWeight.w400, color: foreground, letterSpacing: 0, height: 1.6, ); static TextStyle get bodyMedium => GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w400, color: foreground, letterSpacing: 0, height: 1.6, ); static TextStyle get bodySmall => GoogleFonts.inter( fontSize: 13, fontWeight: FontWeight.w400, color: foregroundSecondary, letterSpacing: 0, height: 1.5, ); static TextStyle get bodyXs => GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w400, color: foregroundMuted, letterSpacing: 0, height: 1.5, ); // 기타 스타일 static TextStyle get bodyMuted => GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w400, color: foregroundMuted, letterSpacing: 0, height: 1.6, ); static TextStyle get labelLarge => GoogleFonts.inter( fontSize: 14, fontWeight: FontWeight.w500, color: foreground, letterSpacing: 0.02, height: 1.4, ); static TextStyle get labelMedium => GoogleFonts.inter( fontSize: 13, fontWeight: FontWeight.w500, color: foreground, letterSpacing: 0.02, height: 1.4, ); static TextStyle get labelSmall => GoogleFonts.inter( fontSize: 12, fontWeight: FontWeight.w500, color: foregroundSecondary, letterSpacing: 0.02, height: 1.4, ); static TextStyle get caption => GoogleFonts.inter( fontSize: 11, fontWeight: FontWeight.w400, color: foregroundMuted, letterSpacing: 0.02, height: 1.4, ); // 코드/모노스페이스 static TextStyle get code => GoogleFonts.jetBrainsMono( fontSize: 13, fontWeight: FontWeight.w400, color: foreground, letterSpacing: 0, height: 1.5, ); // ============= Flutter 테마 데이터 ============= static ThemeData get lightTheme { return ThemeData( useMaterial3: true, colorScheme: const ColorScheme.light( primary: primary, primaryContainer: primaryLight, secondary: secondary, secondaryContainer: secondaryLight, surface: background, surfaceContainerHighest: card, onSurface: foreground, onPrimary: primaryForeground, onSecondary: secondaryForeground, error: error, errorContainer: errorLight, onError: errorForeground, outline: border, outlineVariant: divider, ), scaffoldBackgroundColor: background, textTheme: TextTheme( displayLarge: headingH1, displayMedium: headingH2, displaySmall: headingH3, headlineLarge: headingH3, headlineMedium: headingH4, headlineSmall: headingH5, titleLarge: headingH6, titleMedium: labelLarge, titleSmall: labelMedium, bodyLarge: bodyLarge, bodyMedium: bodyMedium, bodySmall: bodySmall, labelLarge: labelLarge, labelMedium: labelMedium, labelSmall: labelSmall, ), appBarTheme: AppBarTheme( backgroundColor: background, foregroundColor: foreground, elevation: 0, scrolledUnderElevation: 0, shadowColor: Colors.transparent, surfaceTintColor: Colors.transparent, centerTitle: false, titleTextStyle: headingH5, toolbarHeight: 64, iconTheme: const IconThemeData( color: foregroundSecondary, size: 20, ), ), cardTheme: CardThemeData( color: card, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(radiusLg), side: const BorderSide(color: border, width: 1), ), shadowColor: Colors.transparent, margin: EdgeInsets.zero, ), elevatedButtonTheme: ElevatedButtonThemeData( style: ElevatedButton.styleFrom( backgroundColor: primary, foregroundColor: primaryForeground, disabledBackgroundColor: backgroundSecondary, disabledForegroundColor: foregroundMuted, elevation: 0, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(radiusMd), ), minimumSize: const Size(64, 40), padding: const EdgeInsets.symmetric( horizontal: spacing6, vertical: spacing2, ), textStyle: labelMedium.copyWith(fontWeight: FontWeight.w500), ), ), outlinedButtonTheme: OutlinedButtonThemeData( style: OutlinedButton.styleFrom( foregroundColor: foreground, disabledForegroundColor: foregroundMuted, side: const BorderSide(color: border, width: 1), elevation: 0, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(radiusMd), ), minimumSize: const Size(64, 40), padding: const EdgeInsets.symmetric( horizontal: spacing6, vertical: spacing2, ), textStyle: labelMedium.copyWith(fontWeight: FontWeight.w500), ), ), textButtonTheme: TextButtonThemeData( style: TextButton.styleFrom( foregroundColor: primary, disabledForegroundColor: foregroundMuted, elevation: 0, shadowColor: Colors.transparent, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(radiusMd), ), minimumSize: const Size(64, 40), padding: const EdgeInsets.symmetric( horizontal: spacing4, vertical: spacing2, ), textStyle: labelMedium.copyWith(fontWeight: FontWeight.w500), ), ), inputDecorationTheme: InputDecorationTheme( filled: true, fillColor: input, hoverColor: inputHover, contentPadding: const EdgeInsets.symmetric( horizontal: spacing3, vertical: spacing3, ), border: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), borderSide: const BorderSide(color: inputBorder, width: 1), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), borderSide: const BorderSide(color: inputBorder, width: 1), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), borderSide: const BorderSide(color: inputFocus, width: 2), ), errorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), borderSide: const BorderSide(color: error, width: 1), ), focusedErrorBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), borderSide: const BorderSide(color: error, width: 2), ), disabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(radiusMd), borderSide: BorderSide(color: border.withValues(alpha: 0.5), width: 1), ), hintStyle: bodyMedium.copyWith(color: foregroundSubtle), labelStyle: labelMedium.copyWith(color: foregroundSecondary), helperStyle: bodySmall.copyWith(color: foregroundMuted), errorStyle: bodySmall.copyWith(color: error), prefixIconColor: foregroundMuted, suffixIconColor: foregroundMuted, ), dividerTheme: const DividerThemeData( color: divider, thickness: 1, space: 1, ), chipTheme: ChipThemeData( backgroundColor: backgroundSecondary, disabledColor: backgroundSecondary.withValues(alpha: 0.5), selectedColor: primaryLight, secondarySelectedColor: primaryLight, labelStyle: labelSmall, secondaryLabelStyle: labelSmall, padding: const EdgeInsets.symmetric(horizontal: spacing2, vertical: spacing1), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(radiusFull), side: const BorderSide(color: Colors.transparent), ), ), dialogTheme: DialogThemeData( backgroundColor: card, elevation: 0, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(radiusLg), ), titleTextStyle: headingH5, contentTextStyle: bodyMedium, ), tooltipTheme: TooltipThemeData( decoration: BoxDecoration( color: foreground, borderRadius: BorderRadius.circular(radiusMd), ), textStyle: bodySmall.copyWith(color: background), padding: const EdgeInsets.symmetric( horizontal: spacing3, vertical: spacing2, ), ), snackBarTheme: SnackBarThemeData( backgroundColor: foreground, contentTextStyle: bodyMedium.copyWith(color: background), actionTextColor: primaryLight, behavior: SnackBarBehavior.floating, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(radiusMd), ), ), dataTableTheme: DataTableThemeData( headingRowColor: WidgetStateProperty.all(backgroundSecondary), headingTextStyle: labelMedium.copyWith( color: foreground, fontWeight: FontWeight.w600, ), dataTextStyle: bodyMedium, dividerThickness: 1, horizontalMargin: spacing4, columnSpacing: spacing6, headingRowHeight: 48, dataRowMinHeight: 56, dataRowMaxHeight: 56, ), ); } // ============= 유틸리티 메서드 ============= /// 회사 타입에 따른 색상 반환 (Phase 10 업데이트) static Color getCompanyColor(String type) { switch (type.toLowerCase()) { case '본사': case 'headquarters': return companyHeadquarters; // #2563eb - 진한 파랑 case '지점': case 'branch': return companyBranch; // #3b82f6 - 밝은 파랑 case '파트너사': case 'partner': return companyPartner; // #10b981 - 에메랄드 case '고객사': case 'customer': return companyCustomer; // #059669 - 진한 그린 default: return secondary; } } /// 트랜잭션 상태에 따른 색상 반환 (Phase 10 업데이트) static Color getEquipmentStatusColor(String status) { switch (status.toLowerCase()) { case '입고': case 'in': return equipmentIn; // #10b981 - 에메랄드 case '출고': case 'out': return equipmentOut; // #3b82f6 - 블루 case '대여': case 'rent': return equipmentRent; // #8b5cf6 - 퍼플 case '폐기': case 'disposal': return equipmentDisposal; // #6b7280 - 그레이 case '수리중': case 'repair': return equipmentRepair; // #f59e0b - 앰버 case '알수없음': case 'unknown': return equipmentUnknown; // #9ca3af - 라이트 그레이 default: return secondary; } } /// 알림/경고 긴급도에 따른 색상 반환 (Phase 10 신규 추가) static Color getAlertColor(int daysUntilExpiry) { if (daysUntilExpiry < 0) return alertExpired; // 만료됨 if (daysUntilExpiry <= 7) return alertCritical7; // 7일 이내 if (daysUntilExpiry <= 30) return alertWarning30; // 30일 이내 if (daysUntilExpiry <= 60) return alertWarning60; // 60일 이내 return alertNormal; // 60일 이상 } /// 상태별 배경색 반환 (연한 버전) static Color getStatusBackgroundColor(String status) { switch (status.toLowerCase()) { case 'success': case '성공': case '완료': return successLight; case 'warning': case '경고': case '주의': return warningLight; case 'error': case '오류': case '실패': return errorLight; case 'info': case '정보': case '알림': return infoLight; default: return backgroundSecondary; } } }