## 주요 변경사항 ### 아키텍처 개선 - Clean Architecture 패턴 적용 (Domain, Data, Presentation 레이어 분리) - Use Case 패턴 도입으로 비즈니스 로직 캡슐화 - Repository 패턴으로 데이터 접근 추상화 - 의존성 주입 구조 개선 ### 상태 관리 최적화 - 모든 Controller에서 불필요한 상태 관리 로직 제거 - 페이지네이션 로직 통일 및 간소화 - 에러 처리 로직 개선 (에러 메시지 한글화) - 로딩 상태 관리 최적화 ### Mock 서비스 제거 - MockDataService 완전 제거 - 모든 화면을 실제 API 전용으로 전환 - 불필요한 Mock 관련 코드 정리 ### UI/UX 개선 - Overview 화면 대시보드 기능 강화 - 라이선스 만료 알림 위젯 추가 - 사이드바 네비게이션 개선 - 일관된 UI 컴포넌트 사용 ### 코드 품질 - 중복 코드 제거 및 함수 추출 - 파일별 책임 분리 명확화 - 테스트 코드 업데이트 ## 영향 범위 - 모든 화면의 Controller 리팩토링 - API 통신 레이어 구조 개선 - 에러 처리 및 로깅 시스템 개선 ## 향후 계획 - 단위 테스트 커버리지 확대 - 통합 테스트 시나리오 추가 - 성능 모니터링 도구 통합
167 lines
5.0 KiB
Dart
167 lines
5.0 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import '../../services/auth_service.dart';
|
|
import '../../data/models/auth/auth_user.dart';
|
|
|
|
/// 역할 기반 접근 제어를 위한 AuthGuard 위젯
|
|
///
|
|
/// 사용자의 역할을 확인하여 허용된 역할만 child 위젯에 접근할 수 있도록 제어합니다.
|
|
/// 권한이 없는 경우 UnauthorizedScreen을 표시합니다.
|
|
class AuthGuard extends StatelessWidget {
|
|
/// 접근을 허용할 역할 목록
|
|
final List<String> allowedRoles;
|
|
|
|
/// 권한이 있을 때 표시할 위젯
|
|
final Widget child;
|
|
|
|
/// 권한이 없을 때 표시할 커스텀 위젯 (선택사항)
|
|
final Widget? unauthorizedWidget;
|
|
|
|
/// 권한 체크를 건너뛸지 여부 (개발 모드용)
|
|
final bool skipCheck;
|
|
|
|
const AuthGuard({
|
|
super.key,
|
|
required this.allowedRoles,
|
|
required this.child,
|
|
this.unauthorizedWidget,
|
|
this.skipCheck = false,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// 개발 모드에서 권한 체크 건너뛰기
|
|
if (skipCheck) {
|
|
return child;
|
|
}
|
|
|
|
// AuthService에서 현재 사용자 정보 가져오기
|
|
final authService = context.read<AuthService>();
|
|
|
|
return FutureBuilder<AuthUser?>(
|
|
future: authService.getCurrentUser(),
|
|
builder: (context, snapshot) {
|
|
// 로딩 중
|
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
|
|
// 현재 사용자 정보
|
|
final currentUser = snapshot.data;
|
|
|
|
// 로그인하지 않은 경우
|
|
if (currentUser == null) {
|
|
return unauthorizedWidget ?? const UnauthorizedScreen(
|
|
message: '로그인이 필요합니다.',
|
|
);
|
|
}
|
|
|
|
// 역할 확인 - 대소문자 구분 없이 비교
|
|
final userRole = currentUser.role.toLowerCase();
|
|
final hasPermission = allowedRoles.any(
|
|
(role) => role.toLowerCase() == userRole,
|
|
);
|
|
|
|
// 권한이 있는 경우
|
|
if (hasPermission) {
|
|
return child;
|
|
}
|
|
|
|
// 권한이 없는 경우
|
|
return unauthorizedWidget ?? UnauthorizedScreen(
|
|
message: '이 페이지에 접근할 권한이 없습니다.',
|
|
userRole: currentUser.role,
|
|
requiredRoles: allowedRoles,
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 권한이 없을 때 표시되는 기본 화면
|
|
class UnauthorizedScreen extends StatelessWidget {
|
|
final String message;
|
|
final String? userRole;
|
|
final List<String>? requiredRoles;
|
|
|
|
const UnauthorizedScreen({
|
|
super.key,
|
|
required this.message,
|
|
this.userRole,
|
|
this.requiredRoles,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
body: Center(
|
|
child: Container(
|
|
padding: const EdgeInsets.all(32),
|
|
constraints: const BoxConstraints(maxWidth: 400),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Icon(
|
|
Icons.lock_outline,
|
|
size: 80,
|
|
color: Colors.grey,
|
|
),
|
|
const SizedBox(height: 24),
|
|
Text(
|
|
'접근 권한 없음',
|
|
style: Theme.of(context).textTheme.headlineSmall,
|
|
),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
message,
|
|
textAlign: TextAlign.center,
|
|
style: Theme.of(context).textTheme.bodyLarge,
|
|
),
|
|
if (userRole != null) ...[
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
'현재 권한: $userRole',
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
color: Colors.grey,
|
|
),
|
|
),
|
|
],
|
|
if (requiredRoles != null && requiredRoles!.isNotEmpty) ...[
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'필요한 권한: ${requiredRoles!.join(", ")}',
|
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
color: Colors.grey,
|
|
),
|
|
),
|
|
],
|
|
const SizedBox(height: 32),
|
|
ElevatedButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pushReplacementNamed('/dashboard');
|
|
},
|
|
child: const Text('대시보드로 이동'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// 역할 상수 정의
|
|
class UserRole {
|
|
static const String admin = 'Admin';
|
|
static const String manager = 'Manager';
|
|
static const String member = 'Member';
|
|
|
|
/// 관리자 및 매니저 권한
|
|
static const List<String> adminAndManager = [admin, manager];
|
|
|
|
/// 모든 권한
|
|
static const List<String> all = [admin, manager, member];
|
|
|
|
/// 관리자 전용
|
|
static const List<String> adminOnly = [admin];
|
|
} |