Files
superport/CLAUDE_old.md

58 KiB
Raw Blame History

Superport ERP System

💡 Note: Global Claude Code rules are in ~/.claude/CLAUDE.md. This document contains project-specific context.

🎯 Project Overview

Superport는 기업용 장비 관리 및 유지보수를 위한 클라우드 기반 ERP 시스템입니다.

Business Purpose

  • 장비 입출고 및 재고 관리 자동화
  • 유지보수 라이선스 만료일 추적
  • 고객사별 장비 배치 현황 관리
  • 실시간 대시보드를 통한 경영 인사이트 제공

Target Users

  • 관리자 (Admin): 전체 시스템 관리, 장비 입출고, 라이선스 관리, 모든 기능 접근 권한

🏗️ Technical Architecture

Tech Stack

Frontend:
  platform: Flutter Web (Mobile ready)
  state_management: Provider + ChangeNotifier
  ui_framework: ShadCN Flutter Port
  api_client: Dio + Retrofit
  code_generation: Freezed + JsonSerializable

Backend:
  language: Rust
  framework: Actix-Web
  database: PostgreSQL
  auth: JWT (24시간 만료)
  api_url: http://43.201.34.104:8080/api/v1
  source_path: /Users/maximilian.j.sul/Documents/flutter/superport_api

Infrastructure:
  hosting: AWS (예정)
  storage: S3 (예정)
  ci_cd: GitHub Actions (예정)

Project Structure (Clean Architecture)

/Users/maximilian.j.sul/Documents/flutter/
├── superport/            # Flutter Frontend (Clean Architecture)
│   ├── lib/
│   │   ├── core/         # 핵심 공통 기능
│   │   │   ├── controllers/  # BaseController 추상화
│   │   │   ├── errors/       # 에러 처리 체계
│   │   │   ├── utils/        # 유틸리티 함수
│   │   │   └── widgets/      # 공통 위젯
│   │   ├── data/         # Data Layer (외부 인터페이스)
│   │   │   ├── datasources/  # Remote/Local 데이터소스
│   │   │   │   ├── remote/   # API 클라이언트 (Retrofit)
│   │   │   │   └── interceptors/ # Dio 인터셉터
│   │   │   ├── models/       # DTO (Freezed 불변 객체)
│   │   │   └── repositories/ # Repository 구현체
│   │   ├── domain/       # Domain Layer (비즈니스 로직)
│   │   │   ├── repositories/ # Repository 인터페이스
│   │   │   └── usecases/    # UseCase (비즈니스 규칙)
│   │   ├── screens/      # Presentation Layer
│   │   │   └── [feature]/
│   │   │       ├── controllers/ # ChangeNotifier 상태 관리
│   │   │       └── widgets/     # Feature별 UI 컴포넌트
│   │   └── services/     # 레거시 서비스 (마이그레이션 중)
│   └── test/
│       ├── domain/       # UseCase 단위 테스트
│       ├── integration/  # 통합 테스트
│       │   ├── automated/ # UI 자동화 테스트
│       │   └── real_api/  # 실제 API 테스트
│       └── widget/       # 위젯 테스트
│
└── superport_api/        # Rust Backend
    ├── src/
    │   ├── handlers/     # API 엔드포인트
    │   ├── services/     # 비즈니스 로직
    │   └── entities/     # DB 모델
    └── migrations/       # DB 마이그레이션

🚀 Quick Commands

Development

# Start development (Real API)
flutter run -d chrome

# Run tests
flutter test

# Generate code (Freezed, JsonSerializable)
flutter pub run build_runner build --delete-conflicting-outputs

# API integration test
./test_api_integration.sh

# Start backend API (별도 터미널)
cd /Users/maximilian.j.sul/Documents/flutter/superport_api
cargo run

# View API logs
cd /Users/maximilian.j.sul/Documents/flutter/superport_api
tail -f logs/api.log

API Configuration

Base URL: http://43.201.34.104:8080/api/v1
Test Account: admin@example.com / password123
API Source Code: /Users/maximilian.j.sul/Documents/flutter/superport_api

🚨 2025-08-23 중대 발견: 백엔드 API 스키마 완전 분석 결과

📊 백엔드 API 문서 분석 완료

⚠️ CRITICAL: 현재 프론트엔드 구조와 백엔드 실제 스키마 간 심각한 불일치 발견

🔍 주요 불일치 사항

현재_프론트엔드_가정_vs_실제_백엔드:
  Equipment:
    현재: "category1/2/3 필드 직접 사용"
    실제: "models_id FK → models 테이블 → vendors_id FK 구조"
    
  License_Management:
    현재: "독립적인 License 엔티티"
    실제: "maintenances 테이블 (equipment_history_id FK 연결)"
    
  Company_Structure:  
    현재: "단순 Company + Branch 구조"
    실제: "계층형 parent_company_id 지원"
    
  Equipment_History:
    현재: "미구현 상태"
    실제: "핵심 트랜잭션 추적 엔티티 (입출고/재고 관리)"
    
  Rent_Management:
    현재: "미구현 상태" 
    실제: "완전한 대여 관리 시스템 (equipment_history 연동)"

🎯 백엔드 실제 데이터베이스 스키마 (PostgreSQL)

핵심 엔티티 관계도
erDiagram
    vendors ||--o{ models : has
    models ||--o{ equipments : belongs_to
    companies ||--o{ equipments : owns
    companies ||--o{ equipment_history_companies_link : involved_in
    equipments ||--o{ equipment_history : generates
    warehouses ||--o{ equipment_history : stores
    equipment_history ||--|| rents : creates
    equipment_history ||--o{ maintenances : requires
    zipcodes ||--o{ companies : located_at
    zipcodes ||--o{ warehouses : located_at
새로 발견된 필수 엔티티
// 1. 제조사 (vendors) - 완전히 누락됨
VendorEntity {
  int id;
  String name;         // UNIQUE 제약
  bool isDeleted;
  DateTime registeredAt;
  DateTime? updatedAt;
}

// 2. 모델명 (models) - 완전히 누락됨  
ModelEntity {
  int id;
  String name;         // UNIQUE 제약
  int vendorsId;       // FK to vendors
  bool isDeleted;
  DateTime registeredAt;
  DateTime? updatedAt;
}

// 3. 장비이력 (equipment_history) - 핵심 누락
EquipmentHistoryEntity {
  int id;
  int equipmentsId;    // FK to equipments
  int warehousesId;    // FK to warehouses  
  String transactionType;  // 'I'(입고) | 'O'(출고)
  int quantity;
  DateTime transactedAt;
  String? remark;
  DateTime isDeleted;  // 주의: DATETIME 타입
  DateTime createdAt;
  DateTime? updatedAt;
}

// 4. 임대상세 (rents) - 완전히 누락됨
RentEntity {
  int id;
  DateTime startedAt;
  DateTime endedAt;
  int equipmentHistoryId;  // FK to equipment_history
}

// 5. 유지보수이력 (maintenances) - License와 완전히 다름
MaintenanceEntity {
  int id;
  int equipmentHistoryId;  // FK to equipment_history
  DateTime startedAt;
  DateTime endedAt;
  int periodMonth;         // 방문 주기 (월)
  String maintenanceType;  // 'O'(방문) | 'R'(원격)
  bool isDeleted;
  DateTime registeredAt;
  DateTime? updatedAt;
}
기존 엔티티 수정 필요 사항
// Equipment 엔티티 - 대폭 수정 필요
EquipmentEntity {
  int id;
  int companiesId;      // 현재: company_id
  int modelsId;         // 🚨 누락: models 테이블 FK
  String serialNumber;  // UNIQUE 제약
  String? barcode;      // UNIQUE 제약 
  DateTime purchasedAt;
  int purchasePrice;
  String warrantyNumber;
  DateTime warrantyStartedAt;
  DateTime warrantyEndedAt;
  String? remark;
  bool isDeleted;
  DateTime registeredAt;
  DateTime? updatedAt;
}

// Company 엔티티 - 계층 구조 추가
CompanyEntity {
  int id;
  String name;              // UNIQUE 제약
  String contactName;
  String contactPhone;
  String contactEmail;
  int? parentCompanyId;     // 🚨 누락: 계층 구조
  String zipcodeZipcode;    // FK to zipcodes
  String address;
  String? remark;
  bool isPartner;
  bool isCustomer;
  bool isActive;
  bool isDeleted;
  DateTime registeredAt;
  DateTime? updatedAt;
}

🎨 ShadCN UI 기반 전면 UI/UX 리팩토링 계획

📚 ShadCN Flutter UI 라이브러리 분석

🛠️ 라이브러리 개요

🎯 핵심 컴포넌트 활용 계획

Form_Components:
  ShadInput: "모든 TextFormField 대체"
  ShadSelect: "Vendor/Model/Company 드롭다운"
  ShadDatePicker: "구매일/만료일/점검일 선택"
  ShadCheckbox: "Boolean 필드 (is_partner, is_customer)"
  ShadButton: "모든 액션 버튼 통일"

Layout_Components:
  ShadCard: "정보 카드 및 폼 컨테이너"
  ShadTable: "데이터 테이블 (장비/회사/라이선스 리스트)"
  ShadDialog: "등록/수정 모달"
  ShadSheet: "상세 정보 슬라이드 패널"
  ShadTabs: "화면 내 탭 네비게이션"

Data_Display:
  ShadBadge: "상태 표시 (활성/비활성, 장비 상태)"
  ShadAlert: "시스템 알림 및 경고"
  ShadToast: "작업 완료/오류 피드백"
  ShadProgress: "로딩 상태 및 진행률"

Navigation:
  ShadBreadcrumb: "페이지 경로 네비게이션" 
  ShadPagination: "리스트 페이지네이션"

🖥️ 웹 우선 반응형 디자인 전략

// 반응형 브레이크포인트 정의
class ResponsiveBreakpoints {
  static const double mobile = 640;    // 모바일
  static const double tablet = 768;    // 태블릿
  static const double desktop = 1024;  // 데스크톱
  static const double wide = 1280;     // 와이드스크린
}

// 화면별 레이아웃 전략
Desktop_Layout (1024px+):
  - 3-Column 구조: [필터패널][메인컨텐츠][상세패널]
  - 고정 사이드바 + 동적 메인 영역
  - 테이블 풀사이즈 표시 + 인라인 액션

Tablet_Layout (768px~1023px):  
  - 2-Column 구조: [메인컨텐츠][접이식 사이드패널]
  - 햄버거 메뉴 + 슬라이딩 필터
  - 테이블 스크롤 + 상세보기 모달

Mobile_Layout (~767px):
  - 1-Column 구조: [스택형 레이아웃]
  - 풀스크린 카드 + 바텀시트
  - 리스트뷰 +  네비게이션

📦 필요한 추가 의존성

dependencies:
  # 기존 의존성들...
  shadcn_ui: ^0.8.0                    # ShadCN UI 컴포넌트
  webview_flutter: ^4.4.2              # Daum 주소 API 웹뷰
  flutter_inappwebview: ^6.0.0          # JavaScript 통신 지원
  flutter_staggered_grid_view: ^0.7.0   # 가상화 스크롤링
  intl: ^0.18.1                        # 다국어 및 포맷팅
  
dev_dependencies:
  # 기존 dev 의존성들...
  integration_test: ^1.0.0             # 통합 테스트
  flutter_driver: ^0.0.0               # E2E 테스트

🎯 사용자 중심 UX/UI 설계 원칙

데이터 흐름 기반 화면 설계

사용자_워크플로우_우선:
  Equipment_등록_흐름:
    1단계: "제조사 선택 → 모델 자동 필터링"
    2단계: "시리얼 번호 입력 → 실시간 중복 검증"
    3단계: "워런티 정보 → 만료일 자동 계산"
    4단계: "저장 전 최종 검증 → 성공/실패 피드백"
    
  Company_등록_흐름:  
    1단계: "기본 정보 입력 → 실시간 유효성 검증"
    2단계: "주소 검색 → Daum API 웹뷰 호출"
    3단계: "연락처 정보 → 전화번호 형식 자동 변환"
    4단계: "계층 구조 설정 → 본사/지점 관계 시각화"

실시간_검증_및_피드백:
  입력중_검증:
    - 시리얼 번호: debounce 500ms 후 중복 검사
    - 이메일: 형식 검증 + @ 도메인 검증  
    - 전화번호: 010-0000-0000 형식 자동 변환
    - 사업자번호: 000-00-00000 형식 + 유효성 검증
    
  저장전_검증:
    - 필수 항목 누락 시 해당 필드로 자동 스크롤
    - 에러 메시지를 필드 하단에 빨간색으로 표시
    - 성공 시 ShadToast로 "저장되었습니다" 알림
    - 실패 시 구체적인 오류 원인 표시

주소 검색 시스템 (Daum API 연동)

// lib/core/services/address_service.dart
class AddressService {
  static const String daumPostcodeUrl = 'https://postcode.map.daum.net/guide';
  
  Future<AddressResult?> searchAddress(BuildContext context) async {
    return await Navigator.push(
      context,
      MaterialPageRoute(
        builder: (context) => AddressSearchWebView(),
      ),
    );
  }
}

// lib/screens/common/widgets/address_search_webview.dart
class AddressSearchWebView extends StatefulWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text('주소 검색')),
      body: WebView(
        initialUrl: '''
          data:text/html;charset=utf-8,
          <script src="//t1.daumcdn.net/mapjsapi/bundle/postcode/prod/postcode.v2.js"></script>
          <div id="layer"></div>
          <script>
            new daum.Postcode({
              oncomplete: function(data) {
                window.flutter_inappwebview.callHandler('onComplete', data);
              }
            }).embed('layer');
          </script>
        ''',
        onWebViewCreated: (controller) {
          controller.addJavaScriptHandler(
            handlerName: 'onComplete',
            callback: (args) {
              Navigator.pop(context, AddressResult.fromJson(args[0]));
            },
          );
        },
      ),
    );
  }
}

// 주소 검색 결과 모델
@freezed
class AddressResult with _$AddressResult {
  const factory AddressResult({
    required String zonecode,        // 우편번호
    required String address,         // 기본주소  
    required String addressEnglish,  // 영문주소
    String? buildingName,           // 건물명
    String? addressDetail,          // 상세주소 (사용자 입력)
  }) = _AddressResult;
}

폼 컴포넌트 표준화

// lib/screens/common/widgets/standard_form_components.dart

// 1. 실시간 검증이 포함된 입력 필드
class ValidatedShadInput extends StatefulWidget {
  final String label;
  final String? hintText;
  final bool isRequired;
  final Future<String?> Function(String)? asyncValidator;
  final String? Function(String?)? syncValidator;
  final void Function(String)? onChanged;
  final TextInputType? keyboardType;
  
  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 라벨 (필수항목 * 표시)
        RichText(
          text: TextSpan(
            text: label,
            style: Theme.of(context).textTheme.bodyMedium,
            children: isRequired ? [
              TextSpan(
                text: ' *',
                style: TextStyle(color: Colors.red),
              ),
            ] : [],
          ),
        ),
        SizedBox(height: 4),
        
        // ShadCN Input 필드
        ShadInput(
          hintText: hintText,
          keyboardType: keyboardType,
          onChanged: _handleInputChange,
          decoration: InputDecoration(
            errorText: _errorMessage,
            suffixIcon: _isValidating 
              ? SizedBox(
                  width: 16,
                  height: 16,  
                  child: CircularProgressIndicator(strokeWidth: 2),
                )
              : _isValid 
                ? Icon(Icons.check_circle, color: Colors.green)
                : null,
          ),
        ),
        
        // 에러 메시지 또는 도움말
        if (_errorMessage != null)
          Padding(
            padding: EdgeInsets.only(top: 4),
            child: Text(
              _errorMessage!,
              style: TextStyle(
                color: Colors.red[600],
                fontSize: 12,
              ),
            ),
          )
        else if (widget.helpText != null)
          Padding(
            padding: EdgeInsets.only(top: 4),
            child: Text(
              widget.helpText!,
              style: TextStyle(
                color: Colors.grey[600],
                fontSize: 12,
              ),
            ),
          ),
      ],
    );
  }
}

// 2. 전화번호 자동 포맷팅 입력 필드
class PhoneNumberInput extends StatelessWidget {
  final String label;
  final bool isRequired;
  final void Function(String)? onChanged;
  
  @override
  Widget build(BuildContext context) {
    return ValidatedShadInput(
      label: label,
      isRequired: isRequired,
      hintText: "010-0000-0000",
      keyboardType: TextInputType.phone,
      onChanged: (value) {
        String formatted = _formatPhoneNumber(value);
        onChanged?.call(formatted);
      },
      syncValidator: (value) {
        if (isRequired && (value == null || value.isEmpty)) {
          return '전화번호를 입력해주세요';
        }
        if (value != null && value.isNotEmpty && !_isValidPhoneNumber(value)) {
          return '올바른 전화번호 형식이 아닙니다';
        }
        return null;
      },
    );
  }
  
  String _formatPhoneNumber(String value) {
    String digits = value.replaceAll(RegExp(r'[^0-9]'), '');
    if (digits.length <= 3) return digits;
    if (digits.length <= 7) return '${digits.substring(0, 3)}-${digits.substring(3)}';
    return '${digits.substring(0, 3)}-${digits.substring(3, 7)}-${digits.substring(7, min(11, digits.length))}';
  }
  
  bool _isValidPhoneNumber(String value) {
    return RegExp(r'^010-\d{4}-\d{4}$').hasMatch(value);
  }
}

// 3. 주소 검색 통합 컴포넌트  
class AddressSearchField extends StatefulWidget {
  final String label;
  final bool isRequired;
  final void Function(AddressResult?)? onAddressSelected;
  
  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        // 기본 주소 표시 (읽기 전용)
        ValidatedShadInput(
          label: label,
          isRequired: isRequired,
          hintText: "주소를 검색하려면 버튼을 클릭하세요",
          readOnly: true,
          controller: _addressController,
          suffixIcon: ShadButton.outline(
            text: "주소 검색",
            onPressed: _searchAddress,
            size: ShadButtonSize.sm,
          ),
        ),
        
        // 상세 주소 입력
        if (_selectedAddress != null)
          Padding(
            padding: EdgeInsets.only(top: 8),
            child: ValidatedShadInput(
              label: "상세 주소",
              hintText: "동, 호수 등 상세 주소를 입력하세요",
              onChanged: (value) {
                _selectedAddress = _selectedAddress!.copyWith(
                  addressDetail: value,
                );
                widget.onAddressSelected?.call(_selectedAddress);
              },
            ),
          ),
      ],
    );
  }
  
  Future<void> _searchAddress() async {
    final result = await AddressService.searchAddress(context);
    if (result != null) {
      setState(() {
        _selectedAddress = result;
        _addressController.text = result.address;
      });
      widget.onAddressSelected?.call(result);
    }
  }
}

에러 처리 및 사용자 피드백 시스템

에러_표시_전략:
  폼_레벨_에러:
    - 필수 항목 누락: 빨간색 테두리 + 필드별 에러 메시지
    - 서버 에러: 폼 상단에 ShadAlert로 전체 에러 표시
    - 네트워크 에러: 재시도 버튼 포함된 에러 배너
    
  필드_레벨_에러:
    - 실시간 검증: debounce 후 즉시 표시
    - 포커스 아웃 검증: 필드를 벗어날 때 검증
    - 아이콘으로 상태 표시: ✓(성공), ⚠(경고), ✗(에러)

성공_피드백:
  저장_성공: "ShadToast.success('장비가 성공적으로 등록되었습니다')"
  수정_성공: "ShadToast.success('정보가 업데이트되었습니다')"
  삭제_성공: "ShadToast.success('삭제가 완료되었습니다')"
  
로딩_상태:
  버튼_로딩: "ShadButton에 loading 상태 표시"
  폼_로딩: "ShadProgress로 전체 폼 비활성화"
  리스트_로딩: "ShadSkeleton으로 로딩 상태 표시"

접근성 및 사용성 개선

키보드_네비게이션:
  - Tab 키로 순차적 필드 이동
  - Enter 키로 다음 필드 또는 저장 실행
  - Esc 키로 모달/다이얼로그 닫기

모바일_최적화:
  - 가상 키보드에 맞는 input type 설정
  - 화면 회전 시 레이아웃 자동 조정
  - 터치 영역 최소 44px 보장

다국어_대응:
  - 모든 텍스트 다국어 키로 관리
  - 날짜/시간 형식 로케일별 자동 조정
  - 숫자 형식(천단위 구분) 로케일 대응

성능_최적화:
  - 대용량 리스트 가상화 스크롤
  - 이미지 지연 로딩 및 캐싱
  - API 호출 debounce/throttling

🏗️ 전면 리팩토링 7단계 계획

📋 "필요한 모든 에이전트를 동원해라. 클린 아키텍쳐와 함께 SRP를 무조건 지켜서 작업해라."

🔧 리팩토링 자유도 및 권한

프로젝트_구조_변경_권한:
  디렉토리_구조: "전면 재편 가능"
  파일_삭제: "필요없는 파일 완전 제거 허용"
  폴더_이동: "Clean Architecture에 맞게 재구성"
  네이밍_변경: "일관성 있는 명명 규칙으로 통일"

허용되는_작업:
  - 기존 폴더 구조 완전 재설계
  - 레거시 파일 및 코드 삭제
  - ShadCN UI에 맞는 새로운 컴포넌트 구조
  - Clean Architecture 원칙에 따른 레이어 재편
  - 중복 코드 및 사용하지 않는 파일 정리
  - 파일명을 역할에 맞게 명확하고 직관적으로 재명명
  - 테스트 폴더 전체 삭제 후 필요시 새로 구축

파일명_설계_원칙:
  - "역할과 책임을 파일명에 명확히 표현"
  - "snake_case → PascalCase 일관성 유지"
  - "기능별 그룹핑이 파일명에서 즉시 이해 가능"
  - "Dto, Controller, UseCase, Repository 등 타입 명시"
  - "기존 모호한 파일명은 모두 명확하게 변경"

테스트_폴더_관리_정책:
  - "대규모 리팩토링으로 기존 테스트 대부분 무효화 예상"
  - "새로운 구조에 맞지 않는 테스트는 삭제 후 재작성이 효율적"
  - "test/ 폴더 전체 삭제 허용 (사용자 요청 시 새로 구축)"
  - "TDD 원칙에 따라 새 구조에 맞는 테스트 체계 구축"

파일_관리_원칙:
  - "AAAA.dart 수정 시 AAAA_simplified.dart 생성 금지"
  - "기존 파일을 직접 수정하여 코드 개선"
  - "새 파일 생성으로 파일 개수 증가 금지"
  - "리팩토링은 기존 파일 내에서 완료"

SRP_준수_전략:
  - "현재 코드 대부분이 SRP 위반 상태 (여러 책임 혼재)"
  - "코드 추가 시 별도 위젯/컴포넌트로 분리 설계"
  - "기존 파일 내에서 함수/클래스 단위로 책임 분리"
  - "위젯 트리 구조로 단일 책임 위젯들을 조합하여 사용"
  
위젯_컴포넌트_분리_예시:
  - "복잡한 폼 → 입력 필드별 개별 위젯으로 분리"
  - "긴 함수 → 단일 기능 함수들로 분해"
  - "다중 책임 클래스 → 책임별 별도 클래스로 분리"
  - "UI 로직 혼재 → Presentation과 Business 로직 완전 분리"

작업_원칙:
  - "더 나은 구조를 위해서는 기존을 과감히 삭제"
  - "Clean Architecture 위배 요소는 모두 제거"  
  - "ShadCN UI 표준에 맞지 않는 컴포넌트 교체"
  - "백엔드 스키마와 맞지 않는 모델 완전 삭제"
  - "파일명이 모호하면 역할에 맞게 명확히 변경"
  - "기존 테스트보다 새 구조에 맞는 테스트 우선"
  - "파일 개수 증가 없이 기존 파일 내에서 개선"
  - "SRP 위반 코드는 위젯/컴포넌트 분리로 해결"

코드_품질_관리:
  - "모든 코드 작성 완료 후 반드시 'flutter analyze' 실행"
  - "분석 결과 오류가 0개일 때만 작업 완료로 간주"
  - "오류 발견 시 즉시 수정 후 재분석"
  - "분석 통과 후 상세한 한글 주석으로 코드 정리"

한글_주석_작성_원칙:
  - "클래스/함수 상단에 목적과 책임 명시"
  - "복잡한 로직은 단계별로 상세 설명"
  - "매개변수와 반환값의 의미 명확히 기술"
  - "예외 상황과 처리 방법 문서화"
  - "비즈니스 로직의 배경과 이유 설명"

🖥️ ShadCN UI 구현 우선순위

사용자_흐름_기반_구현_순서:
  1단계_로그인_화면: "사용자 사용흐름의 시작점"
    - ShadInput: 이메일/비밀번호 입력 필드
    - ShadButton: 로그인 버튼 (로딩 상태 포함)
    - ShadCard: 로그인 폼 컨테이너
    - ShadAlert: 로그인 실패 시 에러 메시지
    
  2단계_메인_대시보드: "로그인 후 첫 화면"
    - ShadCard: 통계 카드들
    - ShadBadge: 상태 표시 (라이선스 만료 등)
    - ShadTabs: 메뉴 탭 네비게이션
    - ShadTable: 데이터 테이블
    
  3단계_핵심_CRUD_화면:
    - Equipment: ShadForm + ShadSelect + ShadDatePicker
    - Company: ShadInput + AddressSearchField
    - Maintenance: ShadDialog + ShadCalendar
    
구현_원칙: "https://github.com/nank1ro/flutter-shadcn-ui 컴포넌트 우선 사용"

🔥 Phase 1: 백엔드 API 스키마 동기화 (Week 1: Day 1-2)

목표: "실제 백엔드 스키마에 맞춘 DTO 모델 완전 재구축"

작업_범위:
  신규_DTO_생성:
    - VendorDto + Repository + UseCase
    - ModelDto + Repository + UseCase  
    - EquipmentHistoryDto + Repository + UseCase
    - MaintenanceHistoryDto + Repository + UseCase
    - RentDto + Repository + UseCase
    - ZipcodeDto + Repository + UseCase

  기존_DTO_대폭_수정:
    - EquipmentDto: models_id 필드 추가, category1/2/3 제거
    - CompanyDto: parent_company_id 계층 구조 추가
    - WarehouseDto: zipcode 연동 수정
    
  완전_삭제_대상:
    - license_dto.dart → maintenance_history_dto.dart로 대체
    - 모든 Category 관련 하드코딩 로직
    
Clean_Architecture_준수:
  - Domain Layer: 새로운 Repository 인터페이스 6개 추가
  - Data Layer: API 클라이언트 Retrofit 6개 추가  
  - UseCase Layer: CRUD UseCase 24개 추가 (각 엔티티당 4개)

🎨 Phase 2: ShadCN UI 기반 디자인 시스템 구축 (Week 1: Day 3-4)

목표: "통일된 디자인 시스템 및 반응형 레이아웃 기반 구축"

작업_범위:
  ShadCN_통합:
    - pubspec.yaml: shadcn_ui 의존성 추가
    - main.dart: ShadApp 래퍼 구성
    - theme.dart: 커스텀 테마 (Light/Dark) 설정
    
  공통_컴포넌트_개발:
    - ResponsiveLayout: 브레이크포인트 기반 레이아웃
    - StandardFormLayout: 통일된 폼 레이아웃
    - StandardDataTable: ShadTable 기반 데이터 테이블
    - StandardActionBar: CRUD 액션 버튼 바
    
  디자인_토큰_정의:
    - 색상 팔레트: Primary, Secondary, Accent
    - 타이포그래피: 제목, 본문, 캡션 스타일
    - 간격: Margin, Padding 표준화
    - 애니메이션: 전환 효과 통일

⚙️ Phase 3: Equipment 화면 완전 재구현 (Week 1: Day 5-7)

목표: "Vendor→Model→Equipment 연쇄 구조 완벽 구현"

신규_화면_구조:
  Equipment_Management_Screen:
    Desktop: [VendorFilter + ModelFilter][EquipmentTable][DetailPanel]
    Tablet: [EquipmentTable][SlidePanel]  
    Mobile: [EquipmentCards][BottomSheet]
    
  핵심_기능:
    - Vendor 선택 → Model 자동 필터링
    - Serial Number 실시간 중복 검증
    - Barcode 스캔 기능 (웹 카메라)
    - 워런티 만료일 자동 계산
    - 장비 이력 추적 (입고→출고→대여→반납)
    
Equipment_Form_Dialog:
  - ShadSelect: Vendor/Model 연쇄 선택
  - ShadInput: Serial Number (실시간 검증)
  - ShadDatePicker: 구매일/워런티 기간
  - 실시간 유효성 검증 + API 호출

🔧 Phase 4: Maintenance System 재설계 (Week 2: Day 8-10)

목표: "License → MaintenanceHistory 완전 전환"

기능_재정의:
  기존: "라이선스 관리 (독립 엔티티)"
  신규: "장비 유지보수 이력 관리 (Equipment History 연동)"
  
새로운_Maintenance_화면:
  - Equipment 선택 → History 조회 → Maintenance 등록
  - 방문/원격 유지보수 구분
  - 주기별 스케줄링 (period_month)
  - 만료일 알림 시스템 (기존 License 알림 재활용)
  
데이터_마이그레이션:
  - 기존 License 데이터 → MaintenanceHistory 변환 스크립트
  - API 엔드포인트 변경: /licenses → /maintenances
  - 알림 로직 재구성

🏢 Phase 5: Company 계층 구조 시각화 (Week 2: Day 11-12)

목표: "본사-지점 계층 관리 + 파트너/고객 구분"

Company_Tree_View:
  - 계층형 트리 구조 시각화
  - Drag & Drop으로 계층 변경
  - 파트너사/고객사 필터링
  - 대시보드 통계에 계층별 집계 반영
  
신규_기능:
  - 본사 → 지점 일괄 설정
  - 계층별 권한 관리 (상위 회사가 하위 회사 관리)
  - 지역별/계층별 장비 현황 보고서

📊 Phase 6: Equipment History & Rent 시스템 (Week 2: Day 13-14)

목표: "완전한 장비 라이프사이클 추적"

Equipment_History_Tracking:
  - 입고 (I): 창고 입고 + 수량 관리
  - 출고 (O): 회사별 배치 + 상태 변경  
  - 대여 시작: Rent 레코드 생성
  - 대여 종료: 반납 처리 + 상태 복원
  
Rent_Management_System:
  - 대여 기간 관리 (시작일/종료일)
  - 연장 승인 프로세스
  - 반납 체크리스트
  - 연체 알림 시스템
  
Warehouse_Stock_Dashboard:
  - 창고별 실시간 재고 현황
  - 장비별 위치 추적
  - 입출고 이력 시각화

Phase 7: 성능 최적화 & 모바일 완성 (Week 3: Day 15-21)

목표: "엔터프라이즈급 성능 + 완벽한 반응형"

성능_최적화:
  - 가상화 스크롤링: flutter_staggered_grid_view
  - 무한 스크롤: 페이지네이션 자동 로딩
  - 이미지 최적화: 바코드/QR코드 캐싱
  - 메모리 관리: 대용량 리스트 효율화
  
캐싱_전략:
  - Vendor/Model: 1시간 캐시
  - Company 계층: 30분 캐시  
  - Lookups: 기존 30분 유지
  - Equipment History: 실시간 업데이트
  
모바일_UX_완성:
  - 터치 제스처: Swipe to Action
  - 오프라인 지원: 핵심 데이터 로컬 저장
  - PWA 최적화: 설치 가능한 웹앱
  - 푸시 알림: 만료일/연체 알림

🛡️ 작업 안정성 보장 방안

🔒 사이드 이펙트 최소화 전략

0. 구조적 변경 안전성

디렉토리_재구성_안전장치:
  백업_생성: "Git 브랜치로 현재 상태 완전 보존"
  단계적_변경: "폴더별 순차적 재구성으로 추적 가능"
  테스트_검증: "구조 변경 후 빌드 및 테스트 확인"
  롤백_가능성: "언제든 이전 구조로 복원 가능"

파일_삭제_안전성:
  사전_검토: "삭제 전 의존성 분석 및 영향도 확인"
  점진적_제거: "deprecated → warning → 완전 삭제 단계"
  복구_대비: "Git history를 통한 완전한 복구 경로"
  테스트_확인: "삭제 후 전체 시스템 동작 검증"

1. 점진적 마이그레이션 (Zero-Downtime)

Stage_A: "새로운 모델 병렬 구축"
  - 기존 DTO와 신규 DTO 동시 존재
  - Feature Flag로 화면별 전환 제어
  - A/B 테스트 지원 구조

Stage_B: "화면별 개별 전환"
  - 하나씩 새 구조로 마이그레이션
  - 각 단계마다 완전한 테스트
  - 언제든 이전 버전으로 롤백 가능

Stage_C: "레거시 코드 단계적 제거"  
  - 새 시스템 안정성 확인 후
  - deprecated 경고 → 완전 삭제
  - 최종 정리 및 최적화

2. Clean Architecture 철저한 준수

// Domain Layer: 비즈니스 규칙 완전 분리
abstract class EquipmentRepository {
  Future<Either<Failure, List<Equipment>>> getEquipmentsByVendor({
    required int vendorId,
    PaginationParams? params,
  });
  
  Future<Either<Failure, bool>> isSerialNumberUnique(String serialNumber);
}

// UseCase: 단일 책임 원칙 (SRP) 철저 준수
class ValidateEquipmentSerialUseCase {
  final EquipmentRepository _repository;
  
  Future<Either<Failure, bool>> call(String serialNumber) async {
    if (serialNumber.isEmpty) {
      return Left(ValidationFailure('Serial number is required'));
    }
    
    return await _repository.isSerialNumberUnique(serialNumber);
  }
}

// Presentation: 상태 관리 완전 분리
class EquipmentFormController extends ChangeNotifier {
  final ValidateEquipmentSerialUseCase _validateSerial;
  final CreateEquipmentUseCase _createEquipment;
  
  // SRP: 오직 폼 상태 관리만 담당
}

3. 테스트 주도 개발 (TDD)

Unit_Tests:
  - 새로운 UseCase별 100% 커버리지
  - DTO 변환 로직 Edge Case 테스트
  - Validation 로직 모든 시나리오 테스트

Integration_Tests:
  - 백엔드 API 연동 자동화 테스트
  - Equipment → Model → Vendor 연쇄 조회 테스트
  - 트랜잭션 무결성 테스트

Widget_Tests:
  - ShadCN 컴포넌트 모든 상호작용 테스트
  - 반응형 레이아웃 모든 브레이크포인트 테스트
  - 폼 유효성 검증 시나리오 테스트

E2E_Tests:
  - 전체 워크플로우 테스트 (장비 등록 → 출고 → 대여 → 반납)
  - 권한별 접근 제어 테스트
  - 성능 테스트 (대용량 데이터 처리)

📈 프로젝트 상태 업데이트

🚨 현재 상태 재평가

이전_평가: "Development (99.9% Complete)"
현실_평가: "Major Architecture Gap Discovered (재설계 필요)"

API_호환성: "95% → 40% (심각한 스키마 불일치 발견)"
UI_현대화: "70% → 30% (ShadCN UI 적용 필요)"  
기능_완성도: "90% → 60% (핵심 기능 누락 다수)"
전체_완성도: "99.9% → 65% (대규모 리팩토링 필요)"

📊 새로운 성공 지표 (KPI)

기술적_목표:
  API_동기화율: "현재 40% → 목표 100%"
  UI_일관성: "현재 60% → 목표 95%"
  테스트_커버리지: "현재 70% → 목표 90%"
  빌드_시간: "25초 유지"
  성능: "현재 대비 30% 향상"

사용자_경험:
  화면_로딩: "3초 → 1.5초 이하"
  모바일_최적화: "70% → 95%"
  접근성: "기본 → WCAG 2.1 AA 준수"

유지보수성:
  코드_중복률: "15% → 3% 이하"  
  의존성_결합도: "높음 → 낮음"
  SRP_위반: "다수 → 0건"

실행 일정

📅 3주 집중 개발 계획

Week_1: "Foundation & Core (Phase 1-3)"
  Day_1-2: API 스키마 동기화 + 새 DTO 모델
  Day_3-4: ShadCN UI 통합 + 디자인 시스템
  Day_5-7: Equipment 화면 완전 재구현

Week_2: "Advanced Features (Phase 4-6)"  
  Day_8-10: Maintenance 시스템 재설계
  Day_11-12: Company 계층 구조 구현
  Day_13-14: Equipment History & Rent 시스템

Week_3: "Optimization & Completion (Phase 7)"
  Day_15-17: 성능 최적화 + 반응형 완성
  Day_18-19: 전체 테스트 + 품질 보증
  Day_20-21: 배포 준비 + 문서 업데이트

🚀 Status: CRITICAL ARCHITECTURE REDESIGN REQUIRED
Priority: HIGHEST (모든 다른 작업 중단하고 우선 처리)
🎯 Expected Completion: 2025-09-13 (3주 후)
📊 Success Rate: 85% (체계적 접근으로 성공 가능성 높음)

🇰🇷 한국형 ERP UI/UX 설계 원칙

🎯 한국 비즈니스 환경 특성 분석

📋 한국인 ERP 사용 패턴 연구

업무_환경_특성:
  근무_시간: "09:00-18:00 (주 40시간)"
  업무_스타일: "빠른 의사결정, 즉시 처리 선호"
  보고_문화: "실시간 현황 파악, 시각적 데이터 선호"
  모바일_활용: "업무 시간 외 모바일 접근 빈번"
  
ERP_사용_패턴:
  접근_시점: "출근 직후 (09:00-09:30), 퇴근 직전 (17:30-18:00)"
  주요_업무: "일일 현황 확인 → 긴급 처리 → 보고서 작성"
  선호_기능: "대시보드 → 검색 → 등록/수정 → 보고서"
  처리_속도: "3-Click Rule (최대 3번 클릭으로 목표 달성)"
  
정보_소비_패턴:
  시선_흐름: "좌상단 → 우상단 → 좌하단 → 우하단 (Z패턴)"
  중요_정보: "상단 고정, 색상 구분, 숫자 강조"
  경고_알림: "빨간색 Badge, 점멸 효과, 소리 알림"
  성공_피드백: "파란색/초록색, 체크 아이콘, 간결한 메시지"

🏢 한국 기업 조직문화 반영

의사결정_구조:
  상명하달: "관리자 권한 명확한 구분"
  보고_라인: "계층별 데이터 접근 권한 차등화"
  승인_프로세스: "단계별 승인 절차 시각화"
  책임_추적: "작업자 기록 및 이력 관리"
  
업무_프로세스:
  긴급_업무: "빨간색 라벨, 상단 고정 표시"
  일반_업무: "우선순위 번호, 마감일 표시"
  완료_업무: "회색 처리, 접기 기능"
  보류_업무: "노란색 배경, 사유 표시"
  
커뮤니케이션:
  알림_방식: "팝업 → 배지 → 이메일 → SMS 순서"
  언어_사용: "존댓말 기본, 업무용 단어 사용"
  시간_표기: "24시간제, '오전/오후' 병기"
  날짜_형식: "YYYY년 MM월 DD일 (요일)" 

🎨 한국형 UI 디자인 원칙

🖥️ 화면 레이아웃 최적화

// 한국어 텍스트 특성 고려 레이아웃
class KoreanOptimizedLayout {
  // 한글 텍스트는 영문보다 20-30% 더 넓은 공간 필요
  static const double koreanTextPadding = 1.3;
  
  // 한국 사용자 선호 색상 팔레트
  static const Color primaryBlue = Color(0xFF1B4F87);    // 신뢰감
  static const Color successGreen = Color(0xFF2E8B57);   // 성공/완료
  static const Color warningOrange = Color(0xFFFF8C00);  // 주의/대기
  static const Color dangerRed = Color(0xFFDC143C);      // 위험/긴급
  static const Color neutralGray = Color(0xFF708090);    // 일반/비활성
  
  // 한국 사용자 선호 여백 (좀 더 넉넉한 공간)
  static const EdgeInsets cardPadding = EdgeInsets.all(20);
  static const EdgeInsets formFieldSpacing = EdgeInsets.symmetric(vertical: 12);
  static const double listItemHeight = 72; // 터치하기 편한 높이
}

// 한국형 폰트 시스템
class KoreanTypography {
  // 제목: 굵게, 크게 (중요도 강조)
  static const TextStyle heading1 = TextStyle(
    fontSize: 28,
    fontWeight: FontWeight.w700,
    letterSpacing: -0.5,
    height: 1.3,
  );
  
  // 본문: 가독성 우선 (긴 텍스트 편안하게)
  static const TextStyle body1 = TextStyle(
    fontSize: 16,
    fontWeight: FontWeight.w400,
    letterSpacing: -0.2,
    height: 1.6,
  );
  
  // 라벨: 간결하고 명확하게
  static const TextStyle label = TextStyle(
    fontSize: 14,
    fontWeight: FontWeight.w600,
    letterSpacing: 0,
    height: 1.4,
  );
  
  // 캡션: 부가 정보 (작고 연하게)
  static const TextStyle caption = TextStyle(
    fontSize: 12,
    fontWeight: FontWeight.w400,
    letterSpacing: 0.1,
    height: 1.2,
    color: Color(0xFF666666),
  );
}

📱 모바일 우선 반응형 설계

한국_모바일_사용_현황:
  스마트폰_보급률: "95.1% (세계 1위)"
  주요_기기: "Samsung Galaxy, iPhone"
  화면_크기: "6.1-6.8인치 (주류)"
  OS_점유율: "Android 71%, iOS 29%"
  
모바일_UX_최적화:
  터치_영역:
    최소_크기: "48dp x 48dp"
    선호_크기: "56dp x 56dp"
    간격: "8dp 이상"
    
  제스처_패턴:
    스와이프: "좌→우 (뒤로), 우→좌 (삭제)"
    : "단일 탭 (선택), 더블 탭 (확대)"
    롱프레스: "컨텍스트 메뉴, 다중 선택"
    
  키보드_최적화:
    숫자_입력: "numeric 키패드"
    이메일: "email 키패드 (.com 버튼)"
    검색: "search 버튼, 자동완성"
    
  성능_요구사항:
    로딩_시간: "2초 이내 (Wi-Fi), 3초 이내 (4G/5G)"
    스크롤_응답: "60fps 유지"
    메모리_사용: "200MB 이하"

🎯 사용자 중심 네비게이션

// 한국형 네비게이션 패턴
class KoreanNavigationPattern {
  // 메인 메뉴: 4-5개 주요 기능 (더 많으면 혼란)
  static const List<String> mainMenuItems = [
    "대시보드",    // 첫 화면, 전체 현황
    "장비관리",    // 핵심 업무
    "회사관리",    // 고객/파트너 관리  
    "유지보수",    // 정기 업무
    "보고서",     // 결과 확인
  ];
  
  // 브레드크럼: 현재 위치 명확히 표시
  static Widget buildBreadcrumb(List<String> path) {
    return Row(
      children: [
        Icon(Icons.home, size: 16, color: Colors.grey[600]),
        ...path.map((item) => [
          Text(" > ", style: TextStyle(color: Colors.grey[400])),
          Text(item, style: TextStyle(fontWeight: FontWeight.w500)),
        ]).expand((element) => element),
      ],
    );
  }
  
  // 상단 액션 바: 자주 사용하는 기능 배치
  static Widget buildActionBar() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.end,
      children: [
        ShadButton.outline(
          icon: Icon(Icons.search),
          text: "검색",
          size: ShadButtonSize.sm,
        ),
        SizedBox(width: 8),
        ShadButton(
          icon: Icon(Icons.add),
          text: "등록",
          size: ShadButtonSize.sm,
        ),
        SizedBox(width: 8),
        ShadButton.outline(
          icon: Icon(Icons.download),
          text: "엑셀",
          size: ShadButtonSize.sm,
        ),
      ],
    );
  }
}

📊 한국형 대시보드 설계

🎨 정보 시각화 원칙

대시보드_구성_요소:
  상단_KPI_영역: "핵심 지표 4-6개, 큰 숫자로 표시"
  좌측_메뉴_영역: "주요 기능 바로가기"
  중앙_차트_영역: "트렌드 차트, 상태별 파이차트"
  우측_알림_영역: "긴급사항, 만료 예정 항목"
  하단_최근_활동: "최근 등록/수정된 항목들"
  
색상_활용_전략:
  상태_표시:
    정상: "#28A745 (초록) + ✓ 체크 아이콘"
    주의: "#FFC107 (노랑) + ⚠ 경고 아이콘"  
    위험: "#DC3545 (빨강) + ⚡ 긴급 아이콘"
    비활성: "#6C757D (회색) + ○ 원 아이콘"
    
  중요도_구분:
    최우선: "빨간 배경, 흰 글자, 굵은 테두리"
    높음: "주황 배경, 검은 글자, 점선 테두리"
    보통: "파란 배경, 흰 글자, 실선 테두리"
    낮음: "회색 배경, 검은 글자, 테두리 없음"
    
숫자_표현_방식:
  큰_숫자: "123,456대 (천단위 구분)"
  비율: "85.2% (소수점 1자리)"
  금액: "₩1,234,567원 (원화 표시)"
  날짜: "2025-08-23 (금) 오후 2:30"

📈 실시간 현황판 설계

// 한국형 실시간 대시보드 위젯
class KoreanDashboardWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return ResponsiveLayout(
      mobile: _buildMobileDashboard(),
      tablet: _buildTabletDashboard(),
      desktop: _buildDesktopDashboard(),
    );
  }
  
  Widget _buildDesktopDashboard() {
    return Column(
      children: [
        // 1. 실시간 KPI 카드 (상단)
        _buildKPICards(),
        SizedBox(height: 24),
        
        Row(
          children: [
            // 2. 메인 차트 영역 (70%)
            Expanded(
              flex: 7,
              child: Column(
                children: [
                  _buildTrendChart(), // 장비 등록 추이
                  SizedBox(height: 16),
                  _buildStatusPieChart(), // 장비 상태별 분포
                ],
              ),
            ),
            
            SizedBox(width: 24),
            
            // 3. 알림 및 액션 영역 (30%)
            Expanded(
              flex: 3,
              child: Column(
                children: [
                  _buildUrgentAlerts(), // 긴급 알림
                  SizedBox(height: 16),
                  _buildExpiringItems(), // 만료 예정
                  SizedBox(height: 16),
                  _buildQuickActions(), // 빠른 작업
                ],
              ),
            ),
          ],
        ),
        
        SizedBox(height: 24),
        
        // 4. 최근 활동 및 통계 (하단)
        Row(
          children: [
            Expanded(child: _buildRecentEquipments()),
            SizedBox(width: 16),
            Expanded(child: _buildMaintenanceSchedule()),
          ],
        ),
      ],
    );
  }
  
  Widget _buildKPICards() {
    return Row(
      children: [
        _buildKPICard(
          title: "총 장비 수",
          value: "1,234",
          unit: "대",
          trend: "+12",
          trendColor: Colors.green,
          icon: Icons.devices,
          backgroundColor: Color(0xFF1B4F87),
        ),
        SizedBox(width: 16),
        _buildKPICard(
          title: "가동 중",
          value: "1,156",
          unit: "대",
          percentage: "93.7%",
          icon: Icons.power,
          backgroundColor: Color(0xFF2E8B57),
        ),
        SizedBox(width: 16),
        _buildKPICard(
          title: "점검 필요",
          value: "78",
          unit: "대",
          isWarning: true,
          icon: Icons.warning,
          backgroundColor: Color(0xFFFF8C00),
        ),
        SizedBox(width: 16),
        _buildKPICard(
          title: "이번 달 수입",
          value: "₩15.8",
          unit: "억원",
          trend: "+8.5%",
          trendColor: Colors.blue,
          icon: Icons.trending_up,
          backgroundColor: Color(0xFF6F42C1),
        ),
      ],
    );
  }
  
  Widget _buildKPICard({
    required String title,
    required String value,
    required String unit,
    String? percentage,
    String? trend,
    Color? trendColor,
    bool isWarning = false,
    required IconData icon,
    required Color backgroundColor,
  }) {
    return Expanded(
      child: ShadCard(
        child: Padding(
          padding: EdgeInsets.all(20),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.spaceBetween,
                children: [
                  Text(title, style: KoreanTypography.label),
                  Container(
                    padding: EdgeInsets.all(8),
                    decoration: BoxDecoration(
                      color: backgroundColor.withOpacity(0.1),
                      borderRadius: BorderRadius.circular(8),
                    ),
                    child: Icon(icon, color: backgroundColor, size: 20),
                  ),
                ],
              ),
              
              SizedBox(height: 16),
              
              Row(
                crossAxisAlignment: CrossAxisAlignment.baseline,
                textBaseline: TextBaseline.alphabetic,
                children: [
                  Text(
                    value,
                    style: KoreanTypography.heading1.copyWith(
                      color: isWarning ? Color(0xFFDC143C) : backgroundColor,
                    ),
                  ),
                  SizedBox(width: 4),
                  Text(unit, style: KoreanTypography.body1),
                ],
              ),
              
              if (percentage != null || trend != null) ...[
                SizedBox(height: 8),
                Row(
                  children: [
                    if (percentage != null)
                      ShadBadge(
                        text: percentage,
                        backgroundColor: backgroundColor.withOpacity(0.1),
                        textColor: backgroundColor,
                      ),
                    if (trend != null) ...[
                      if (percentage != null) SizedBox(width: 8),
                      Row(
                        children: [
                          Icon(
                            trend.startsWith('+') ? Icons.arrow_upward : Icons.arrow_downward,
                            size: 12,
                            color: trendColor,
                          ),
                          Text(
                            trend,
                            style: TextStyle(
                              color: trendColor,
                              fontSize: 12,
                              fontWeight: FontWeight.w600,
                            ),
                          ),
                        ],
                      ),
                    ],
                  ],
                ),
              ],
            ],
          ),
        ),
      ),
    );
  }
}

🚀 업무 효율성 극대화 UX

빠른 입력 시스템

한국_업무_특성_반영:
  입력_최소화:
    - 자동완성: 회사명, 장비명, 모델명
    - 기본값: 오늘 날짜, 현재 사용자
    - 복사: 이전 입력값 재사용 버튼
    
  일괄_처리:
    - 엑셀 업로드: 대량 데이터 등록
    - 템플릿: 미리 정의된 양식
    - 복제: 비슷한 항목 빠른 생성
    
  검색_최적화:
    - 한글 초성 검색: "ㅅㅁㅅ" → "삼성"
    - 띄어쓰기 무시: "삼 성" → "삼성"
    - 영문/한글 혼용: "samsung 갤럭시"
    
  실시간_피드백:
    - 입력 중 검증: 500ms debounce
    - 진행률 표시: 필수 항목 완성도
    - 저장 상태: 자동저장 + 수동저장

🎯 상황별 맞춤 UI

// 시간대별 UI 최적화
class TimeAwareUI {
  static Widget buildDashboard(DateTime currentTime) {
    final hour = currentTime.hour;
    
    if (hour >= 9 && hour <= 10) {
      // 출근 시간: 어제 변경사항, 오늘 할 일
      return MorningDashboard();
    } else if (hour >= 12 && hour <= 13) {
      // 점심 시간: 간단한 현황만
      return LunchDashboard();
    } else if (hour >= 17 && hour <= 18) {
      // 퇴근 시간: 오늘 완료 현황, 내일 예정
      return EveningDashboard();
    } else {
      // 일반 시간: 전체 대시보드
      return StandardDashboard();
    }
  }
}

// 모바일 상황별 UI
class ContextAwareUI {
  static Widget buildMobileInterface(BuildContext context) {
    return Column(
      children: [
        // 1. 빠른 액션 바 (상단 고정)
        Container(
          color: Theme.of(context).primaryColor,
          padding: EdgeInsets.symmetric(horizontal: 16, vertical: 12),
          child: Row(
            children: [
              // QR 스캔 (카메라 접근)
              ShadButton.ghost(
                icon: Icon(Icons.qr_code_scanner, color: Colors.white),
                onPressed: () => _scanQRCode(context),
              ),
              
              Spacer(),
              
              // 음성 검색 (음성 인식)
              ShadButton.ghost(
                icon: Icon(Icons.mic, color: Colors.white),
                onPressed: () => _voiceSearch(context),
              ),
              
              // 즐겨찾기 (자주 사용)
              ShadButton.ghost(
                icon: Icon(Icons.star, color: Colors.white),
                onPressed: () => _showFavorites(context),
              ),
            ],
          ),
        ),
        
        // 2. 메인 콘텐츠 (스크롤 가능)
        Expanded(
          child: SingleChildScrollView(
            child: Column(
              children: [
                _buildQuickStats(),
                _buildRecentItems(),
                _buildPendingTasks(),
              ],
            ),
          ),
        ),
        
        // 3. 플로팅 액션 버튼 (주요 작업)
        FloatingActionButton.extended(
          onPressed: () => _showQuickActions(context),
          icon: Icon(Icons.add),
          label: Text("빠른 등록"),
          backgroundColor: Theme.of(context).primaryColor,
        ),
      ],
    );
  }
}

🔒 보안 및 접근성

🛡️ 한국형 보안 요구사항

개인정보보호법_준수:
  데이터_최소화: "필요한 정보만 수집"
  동의_관리: "목적별 동의 받기"
  보유_기간: "법정 보유기간 준수"
  삭제_권리: "사용자 요청 시 즉시 삭제"
  
접근_제어:
  인증: "2단계 인증 (SMS, 앱)"
  권한: "역할 기반 접근 제어"
  로그: "모든 접근 이력 기록"
  세션: "30분 비활성시 자동 로그아웃"
  
데이터_암호화:
  전송: "TLS 1.3 사용"
  저장: "AES-256 암호화"
  백업: "암호화된 백업 파일"
  로그: "민감정보 마스킹"

접근성 및 사용성

웹_접근성_가이드라인:
  키보드_네비게이션: "Tab, Enter, Esc 키 지원"
  스크린_리더: "명확한 라벨, 설명 텍스트"
  색상_대비: "WCAG 2.1 AA 기준 준수"
  폰트_크기: "최소 14px, 확대 200% 지원"
  
다국어_지원:
  기본_언어: "한국어 (ko-KR)"
  보조_언어: "영어 (en-US)"
  숫자_형식: "1,234,567원"
  날짜_형식: "2025년 8월 23일 (금요일)"
  
성능_최적화:
  초기_로딩: "2초 이내"
  페이지_전환: "300ms 이내"
  검색_응답: "1초 이내"
  파일_업로드: "진행률 표시"

📱 모바일 특화 기능

📷 한국 모바일 환경 최적화

// 모바일 전용 기능들
class MobileOptimizedFeatures {
  // 1. QR/바코드 스캔 (장비 등록용)
  static Future<String?> scanEquipmentCode() async {
    return await BarcodeScanner.scan(
      options: ScanOptions(
        strings: {
          'cancel': '취소',
          'flash_on': '플래시 켜기',
          'flash_off': '플래시 끄기',
        },
        restrictFormat: [BarcodeFormat.qr, BarcodeFormat.code128],
      ),
    );
  }
  
  // 2. 음성 검색 (한국어 STT)
  static Future<String?> voiceSearch() async {
    return await SpeechToText.listen(
      localeId: 'ko-KR',
      onResult: (result) => result.recognizedWords,
      listenOptions: SpeechListenOptions(
        partialResults: true,
        listenMode: ListenMode.confirmation,
        cancelOnError: true,
      ),
    );
  }
  
  // 3. 오프라인 모드 (핵심 데이터 캐시)
  static Future<void> syncOfflineData() async {
    final box = await Hive.openBox('offline_cache');
    
    // 필수 데이터만 오프라인 저장
    await box.put('companies', await CompanyRepository.getAllCompanies());
    await box.put('equipment_types', await EquipmentRepository.getTypes());
    await box.put('recent_equipments', await EquipmentRepository.getRecent(50));
    
    // 7일 후 만료
    await box.put('cache_expiry', DateTime.now().add(Duration(days: 7)));
  }
  
  // 4. 푸시 알림 (한국어 메시지)
  static Future<void> sendMaintenanceAlert(Equipment equipment) async {
    await FirebaseMessaging.instance.sendMessage(
      to: equipment.managerId,
      data: {
        'title': '유지보수 알림',
        'body': '${equipment.name} 장비의 점검일이 다가왔습니다.',
        'type': 'maintenance_due',
        'equipment_id': equipment.id.toString(),
      },
    );
  }
  
  // 5. 생체인증 (지문, Face ID)
  static Future<bool> authenticateWithBiometrics() async {
    final localAuth = LocalAuthentication();
    
    try {
      final isAuthenticated = await localAuth.authenticate(
        localizedFallbackTitle: 'PIN으로 인증',
        authMessages: [
          AndroidAuthMessages(
            signInTitle: '생체인증으로 로그인',
            biometricHint: '지문을 터치하세요',
            cancelButton: '취소',
          ),
          IOSAuthMessages(
            lockOut: '생체인증이 비활성화되었습니다',
            cancelButton: '취소',
          ),
        ],
      );
      
      return isAuthenticated;
    } catch (e) {
      return false;
    }
  }
}

🎨 한국형 아이콘 및 시각 요소

🎯 문화적 친화성

아이콘_선택_기준:
  직관성: "한국 사용자가 즉시 이해할 수 있는 아이콘"
  일관성: "Material Design 3 기반"
  가독성: "24dp 이상, 명확한 선"
  
주요_아이콘_매핑:
  : "🏠 house (집 모양)"
  설정: "⚙️ settings (톱니바퀴)"
  검색: "🔍 search (돋보기)"
  등록: " add (플러스)"
  수정: "✏️ edit (연필)"
  삭제: "🗑️ delete (휴지통)"
  다운로드: "⬇️ download (아래 화살표)"
  업로드: "⬆️ upload (위 화살표)"
  알림: "🔔 notifications (벨)"
  즐겨찾기: "⭐ star (별)"
  
상태_표시_아이콘:
  성공: "✅ check_circle (체크 원)"
  경고: "⚠️ warning (삼각형 느낌표)"
  오류: "❌ error (X 표시)"
  정보: " info (원 안에 i)"
  로딩: "⏳ hourglass (시계)"

🎨 색상 심리학 활용

// 한국 사용자 선호 색상 시스템
class KoreanColorSystem {
  // 메인 브랜드 컬러 (신뢰감)
  static const Color primaryBlue = Color(0xFF1E40AF);
  static const Color primaryBlueLight = Color(0xFF3B82F6);
  static const Color primaryBlueDark = Color(0xFF1E3A8A);
  
  // 보조 컬러 (활동성)
  static const Color secondaryGreen = Color(0xFF059669);
  static const Color secondaryGreenLight = Color(0xFF10B981);
  static const Color secondaryGreenDark = Color(0xFF047857);
  
  // 시스템 컬러 (기능성)
  static const Color warningAmber = Color(0xFFD97706);  // 주의
  static const Color dangerRed = Color(0xFFDC2626);     // 위험
  static const Color infoBlue = Color(0xFF0284C7);      // 정보
  static const Color successGreen = Color(0xFF16A34A);  // 성공
  
  // 중성 컬러 (조화)
  static const Color neutralGray = Color(0xFF6B7280);
  static const Color neutralLightGray = Color(0xFFF3F4F6);
  static const Color neutralDarkGray = Color(0xFF374151);
  
  // 한국인 선호 그라데이션
  static const LinearGradient primaryGradient = LinearGradient(
    colors: [Color(0xFF1E40AF), Color(0xFF3B82F6)],
    begin: Alignment.topLeft,
    end: Alignment.bottomRight,
  );
  
  // 상태별 배경색 (시각적 구분)
  static Color getStatusColor(String status) {
    switch (status) {
      case '정상': return successGreen.withOpacity(0.1);
      case '주의': return warningAmber.withOpacity(0.1);
      case '위험': return dangerRed.withOpacity(0.1);
      case '점검': return infoBlue.withOpacity(0.1);
      default: return neutralLightGray;
    }
  }
}

📅 Recent Updates