feat: 사용자 관리 시스템 백엔드 API 호환성 대폭 개선
- UserRemoteDataSource: API v0.2.1 스펙 완전 대응 • 응답 형식 통일 (success: true 구조) • 페이지네이션 처리 개선 • 에러 핸들링 강화 • 불필요한 파라미터 제거 (includeInactive 등) - UserDto 모델 현대화: • 서버 응답 구조와 100% 일치 • 도메인 모델 변환 메서드 추가 • Freezed 불변성 패턴 완성 - User 도메인 모델 신규 구현: • Clean Architecture 원칙 준수 • UserRole enum 타입 안전성 강화 • 비즈니스 로직 캡슐화 - 사용자 관련 UseCase 리팩토링: • Repository 패턴 완전 적용 • Either<Failure, Success> 에러 처리 • 의존성 주입 최적화 - UI 컨트롤러 및 화면 개선: • API 응답 변경사항 반영 • 사용자 권한 표시 정확성 향상 • 폼 검증 로직 강화 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -215,6 +215,8 @@ class _AppLayoutState extends State<AppLayout>
|
||||
return '장비 관리';
|
||||
case Routes.company:
|
||||
return '회사 관리';
|
||||
case Routes.user:
|
||||
return '사용자 관리';
|
||||
case Routes.license:
|
||||
return '유지보수 관리';
|
||||
case Routes.warehouseLocation:
|
||||
@@ -241,6 +243,8 @@ class _AppLayoutState extends State<AppLayout>
|
||||
return ['홈', '장비 관리', '대여'];
|
||||
case Routes.company:
|
||||
return ['홈', '회사 관리'];
|
||||
case Routes.user:
|
||||
return ['홈', '사용자 관리'];
|
||||
case Routes.license:
|
||||
return ['홈', '유지보수 관리'];
|
||||
case Routes.warehouseLocation:
|
||||
@@ -302,10 +306,10 @@ class _AppLayoutState extends State<AppLayout>
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// F-Pattern: 2차 시선 - 페이지 헤더 + 액션
|
||||
_buildPageHeader(),
|
||||
|
||||
const SizedBox(height: ShadcnTheme.spacing4),
|
||||
// F-Pattern: 2차 시선 - 페이지 헤더 + 액션 (주석처리)
|
||||
// _buildPageHeader(),
|
||||
//
|
||||
// const SizedBox(height: ShadcnTheme.spacing4),
|
||||
|
||||
// F-Pattern: 주요 작업 영역
|
||||
Expanded(
|
||||
@@ -560,12 +564,14 @@ class _AppLayoutState extends State<AppLayout>
|
||||
);
|
||||
}
|
||||
|
||||
/// F-Pattern 2차 시선: 페이지 헤더 (제목 + 주요 액션 버튼)
|
||||
/// F-Pattern 2차 시선: 페이지 헤더 (간소화된 제목)
|
||||
Widget _buildPageHeader() {
|
||||
final breadcrumbs = _getBreadcrumbs();
|
||||
final breadcrumbs = _getBreadcrumbs(); // 변수는 유지 (향후 사용 가능)
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.all(ShadcnTheme.spacing6),
|
||||
// BaseListScreen과 동일한 폭을 위해 마진 추가
|
||||
margin: const EdgeInsets.symmetric(horizontal: ShadcnTheme.spacing6), // 24px 마진 추가
|
||||
padding: const EdgeInsets.all(ShadcnTheme.spacing3), // 12px 내부 패딩
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.background,
|
||||
borderRadius: BorderRadius.circular(ShadcnTheme.radiusLg),
|
||||
@@ -574,82 +580,51 @@ class _AppLayoutState extends State<AppLayout>
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 왼쪽: 페이지 제목 + 브레드크럼
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 페이지 제목
|
||||
Text(
|
||||
_getPageTitle(),
|
||||
style: ShadcnTheme.headingH4,
|
||||
),
|
||||
const SizedBox(height: ShadcnTheme.spacing1),
|
||||
// 브레드크럼
|
||||
Row(
|
||||
children: [
|
||||
for (int i = 0; i < breadcrumbs.length; i++) ...[
|
||||
if (i > 0) ...[
|
||||
const SizedBox(width: ShadcnTheme.spacing1),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
size: 14,
|
||||
color: ShadcnTheme.foregroundSubtle,
|
||||
),
|
||||
const SizedBox(width: ShadcnTheme.spacing1),
|
||||
],
|
||||
Text(
|
||||
breadcrumbs[i],
|
||||
style: i == breadcrumbs.length - 1
|
||||
? ShadcnTheme.bodySmall.copyWith(
|
||||
color: ShadcnTheme.foreground,
|
||||
fontWeight: FontWeight.w500,
|
||||
)
|
||||
: ShadcnTheme.bodySmall.copyWith(
|
||||
color: ShadcnTheme.foregroundMuted,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
// 페이지 제목 (좌측 패딩 + 작은 텍스트)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: ShadcnTheme.spacing2), // 좌측 패딩 추가
|
||||
child: Text(
|
||||
_getPageTitle(),
|
||||
style: ShadcnTheme.bodySmall.copyWith(
|
||||
fontWeight: FontWeight.w500, // 약간 두껴게
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// 오른쪽: 페이지별 주요 액션 버튼들
|
||||
if (_currentRoute != Routes.home) ...[
|
||||
Row(
|
||||
children: [
|
||||
// 새로고침
|
||||
ShadcnButton(
|
||||
text: '',
|
||||
icon: Icon(Icons.refresh, size: 18),
|
||||
onPressed: () {
|
||||
// 페이지 새로고침
|
||||
setState(() {});
|
||||
_loadLicenseExpirySummary(); // 라이선스 만료 정보도 새로고침
|
||||
},
|
||||
variant: ShadcnButtonVariant.ghost,
|
||||
size: ShadcnButtonSize.small,
|
||||
),
|
||||
const SizedBox(width: ShadcnTheme.spacing2),
|
||||
// 추가 버튼 (리스트 페이지에서만)
|
||||
if (_isListPage()) ...[
|
||||
ShadcnButton(
|
||||
text: '추가',
|
||||
icon: Icon(Icons.add, size: 18),
|
||||
onPressed: () {
|
||||
_handleAddAction();
|
||||
},
|
||||
variant: ShadcnButtonVariant.primary,
|
||||
size: ShadcnButtonSize.small,
|
||||
|
||||
// 브레드크럼 (주석처리)
|
||||
/*
|
||||
const SizedBox(height: ShadcnTheme.spacing1),
|
||||
// 브레드크럼
|
||||
Row(
|
||||
children: [
|
||||
for (int i = 0; i < breadcrumbs.length; i++) ...[
|
||||
if (i > 0) ...[
|
||||
const SizedBox(width: ShadcnTheme.spacing1),
|
||||
Icon(
|
||||
Icons.chevron_right,
|
||||
size: 14,
|
||||
color: ShadcnTheme.foregroundSubtle,
|
||||
),
|
||||
const SizedBox(width: ShadcnTheme.spacing1),
|
||||
],
|
||||
Text(
|
||||
breadcrumbs[i],
|
||||
style: i == breadcrumbs.length - 1
|
||||
? ShadcnTheme.bodySmall.copyWith(
|
||||
color: ShadcnTheme.foreground,
|
||||
fontWeight: FontWeight.w500,
|
||||
)
|
||||
: ShadcnTheme.bodySmall.copyWith(
|
||||
color: ShadcnTheme.foregroundMuted,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
*/
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -663,6 +638,7 @@ class _AppLayoutState extends State<AppLayout>
|
||||
Routes.equipmentOutList,
|
||||
Routes.equipmentRentList,
|
||||
Routes.company,
|
||||
Routes.user,
|
||||
Routes.license,
|
||||
Routes.warehouseLocation,
|
||||
].contains(_currentRoute);
|
||||
@@ -682,6 +658,9 @@ class _AppLayoutState extends State<AppLayout>
|
||||
case Routes.company:
|
||||
addRoute = '/company/add';
|
||||
break;
|
||||
case Routes.user:
|
||||
addRoute = '/user/add';
|
||||
break;
|
||||
case Routes.license:
|
||||
addRoute = '/license/add';
|
||||
break;
|
||||
@@ -960,6 +939,14 @@ class SidebarMenu extends StatelessWidget {
|
||||
badge: null,
|
||||
),
|
||||
|
||||
_buildMenuItem(
|
||||
icon: Icons.people_outlined,
|
||||
title: '사용자 관리',
|
||||
route: Routes.user,
|
||||
isActive: currentRoute == Routes.user,
|
||||
badge: null,
|
||||
),
|
||||
|
||||
_buildMenuItem(
|
||||
icon: Icons.support_outlined,
|
||||
title: '유지보수 관리',
|
||||
@@ -1129,6 +1116,8 @@ class SidebarMenu extends StatelessWidget {
|
||||
return Icons.warehouse;
|
||||
case Icons.business_outlined:
|
||||
return Icons.business;
|
||||
case Icons.people_outlined:
|
||||
return Icons.people;
|
||||
case Icons.support_outlined:
|
||||
return Icons.support;
|
||||
case Icons.bug_report_outlined:
|
||||
|
||||
Reference in New Issue
Block a user