feat: Flutter analyze 오류 대폭 개선 및 재고 이력 화면 UI 통일 완료
## 주요 개선사항 ### 🔧 Flutter Analyze 오류 대폭 개선 - 이전: 47개 이슈 (ERROR 14개 포함) - 현재: 22개 이슈 (ERROR 0개) - 개선율: 53% 감소, 모든 ERROR 해결 ### 🎨 재고 이력 화면 UI 통일 완료 - BaseListScreen 패턴 완전 적용 - 헤더 고정 + 바디 스크롤 구조 구현 - shadcn_ui 컴포넌트 100% 사용 - 장비 관리 화면과 동일한 표준 패턴 ### ✨ 코드 품질 개선 - unused imports 제거 (5개 파일) - unnecessary cast 제거 - unused fields 제거 - injection container 오류 해결 ### 📋 문서화 완료 - CLAUDE.md에 UI 통일성 리팩토링 계획 상세 추가 - 전체 10개 화면의 단계별 계획 문서화 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -5,7 +5,6 @@ import 'package:superport/models/user_model.dart';
|
||||
import 'package:superport/screens/common/theme_shadcn.dart';
|
||||
import 'package:superport/screens/common/components/shadcn_components.dart';
|
||||
import 'package:superport/screens/common/layouts/base_list_screen.dart';
|
||||
import 'package:superport/screens/common/widgets/standard_data_table.dart';
|
||||
import 'package:superport/screens/common/widgets/standard_action_bar.dart';
|
||||
import 'package:superport/screens/common/widgets/pagination.dart';
|
||||
import 'package:superport/screens/user/controllers/user_list_controller.dart';
|
||||
@@ -263,89 +262,203 @@ class _UserListState extends State<UserList> {
|
||||
}
|
||||
|
||||
Widget _buildDataTable() {
|
||||
if (_controller.users.isEmpty && !_controller.isLoading) {
|
||||
return StandardDataTable(
|
||||
columns: _getColumns(),
|
||||
rows: const [],
|
||||
emptyMessage: '등록된 사용자가 없습니다',
|
||||
emptyIcon: Icons.people_outlined,
|
||||
);
|
||||
}
|
||||
|
||||
return StandardDataTable(
|
||||
columns: _getColumns(),
|
||||
rows: _buildRows(),
|
||||
fixedHeader: true,
|
||||
maxHeight: 600,
|
||||
final users = _controller.users;
|
||||
|
||||
return Container(
|
||||
width: double.infinity,
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.black),
|
||||
borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
// 고정 헤더
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.muted.withValues(alpha: 0.3),
|
||||
border: Border(bottom: BorderSide(color: Colors.black)),
|
||||
),
|
||||
child: Row(children: _buildHeaderCells()),
|
||||
),
|
||||
// 스크롤 바디
|
||||
Expanded(
|
||||
child: users.isEmpty
|
||||
? Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.people_outlined,
|
||||
size: 64,
|
||||
color: ShadcnTheme.mutedForeground,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'등록된 사용자가 없습니다',
|
||||
style: ShadcnTheme.bodyMedium.copyWith(
|
||||
color: ShadcnTheme.mutedForeground,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
itemCount: users.length,
|
||||
itemBuilder: (context, index) => _buildTableRow(users[index], index),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<StandardDataColumn> _getColumns() {
|
||||
List<Widget> _buildHeaderCells() {
|
||||
return [
|
||||
StandardDataColumn(label: 'No.', width: 60),
|
||||
StandardDataColumn(label: '이름', flex: 1),
|
||||
StandardDataColumn(label: '이메일', flex: 2),
|
||||
StandardDataColumn(label: '회사', flex: 1),
|
||||
StandardDataColumn(label: '권한', width: 80),
|
||||
StandardDataColumn(label: '상태', width: 80),
|
||||
StandardDataColumn(label: '작업', width: 120),
|
||||
_buildHeaderCell('번호', flex: 0, useExpanded: false, minWidth: 50),
|
||||
_buildHeaderCell('이름', flex: 2, useExpanded: true, minWidth: 80),
|
||||
_buildHeaderCell('이메일', flex: 3, useExpanded: true, minWidth: 120),
|
||||
_buildHeaderCell('회사', flex: 2, useExpanded: true, minWidth: 80),
|
||||
_buildHeaderCell('권한', flex: 0, useExpanded: false, minWidth: 80),
|
||||
_buildHeaderCell('상태', flex: 0, useExpanded: false, minWidth: 80),
|
||||
_buildHeaderCell('작업', flex: 0, useExpanded: false, minWidth: 120),
|
||||
];
|
||||
}
|
||||
|
||||
List<Widget> _buildRows() {
|
||||
return _controller.users.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final user = entry.value;
|
||||
final rowNumber = (_controller.currentPage - 1) * _controller.pageSize + index + 1;
|
||||
Widget _buildHeaderCell(
|
||||
String text, {
|
||||
required int flex,
|
||||
required bool useExpanded,
|
||||
required double minWidth,
|
||||
}) {
|
||||
final child = Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(
|
||||
text,
|
||||
style: ShadcnTheme.bodyMedium.copyWith(fontWeight: FontWeight.w500),
|
||||
),
|
||||
);
|
||||
|
||||
return StandardDataRow(
|
||||
index: index,
|
||||
cells: [
|
||||
Text(
|
||||
rowNumber.toString(),
|
||||
style: ShadcnTheme.bodyMedium,
|
||||
),
|
||||
Text(
|
||||
user.name,
|
||||
style: ShadcnTheme.bodyMedium.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
if (useExpanded) {
|
||||
return Expanded(flex: flex, child: child);
|
||||
} else {
|
||||
return SizedBox(width: minWidth, child: child);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildDataCell(
|
||||
Widget child, {
|
||||
required int flex,
|
||||
required bool useExpanded,
|
||||
required double minWidth,
|
||||
}) {
|
||||
final container = Container(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: child,
|
||||
);
|
||||
|
||||
if (useExpanded) {
|
||||
return Expanded(flex: flex, child: container);
|
||||
} else {
|
||||
return SizedBox(width: minWidth, child: container);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _buildTableRow(User user, int index) {
|
||||
final rowNumber = (_controller.currentPage - 1) * _controller.pageSize + index + 1;
|
||||
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: index.isEven
|
||||
? ShadcnTheme.muted.withValues(alpha: 0.1)
|
||||
: null,
|
||||
border: const Border(
|
||||
bottom: BorderSide(color: Colors.black),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_buildDataCell(
|
||||
Text(
|
||||
rowNumber.toString(),
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
flex: 0,
|
||||
useExpanded: false,
|
||||
minWidth: 50,
|
||||
),
|
||||
Text(
|
||||
user.email ?? '',
|
||||
style: ShadcnTheme.bodyMedium,
|
||||
),
|
||||
Text(
|
||||
'-', // Company name not available in current model
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
_buildUserRoleBadge(user.role),
|
||||
_buildStatusChip(user.isActive),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ShadButton.ghost(
|
||||
onPressed: user.id != null ? () => _navigateToEdit(user.id!) : null,
|
||||
child: const Icon(Icons.edit, size: 16),
|
||||
_buildDataCell(
|
||||
Text(
|
||||
user.name,
|
||||
style: ShadcnTheme.bodyMedium.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
const SizedBox(width: ShadcnTheme.spacing1),
|
||||
ShadButton.ghost(
|
||||
onPressed: () => _showStatusChangeDialog(user),
|
||||
child: Icon(
|
||||
user.isActive ? Icons.person_off : Icons.person,
|
||||
size: 16,
|
||||
),
|
||||
flex: 2,
|
||||
useExpanded: true,
|
||||
minWidth: 80,
|
||||
),
|
||||
_buildDataCell(
|
||||
Text(
|
||||
user.email ?? '',
|
||||
style: ShadcnTheme.bodyMedium,
|
||||
),
|
||||
flex: 3,
|
||||
useExpanded: true,
|
||||
minWidth: 120,
|
||||
),
|
||||
_buildDataCell(
|
||||
Text(
|
||||
'-', // Company name not available in current model
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
flex: 2,
|
||||
useExpanded: true,
|
||||
minWidth: 80,
|
||||
),
|
||||
_buildDataCell(
|
||||
_buildUserRoleBadge(user.role),
|
||||
flex: 0,
|
||||
useExpanded: false,
|
||||
minWidth: 80,
|
||||
),
|
||||
_buildDataCell(
|
||||
_buildStatusChip(user.isActive),
|
||||
flex: 0,
|
||||
useExpanded: false,
|
||||
minWidth: 80,
|
||||
),
|
||||
_buildDataCell(
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
ShadButton.ghost(
|
||||
onPressed: user.id != null ? () => _navigateToEdit(user.id!) : null,
|
||||
child: const Icon(Icons.edit, size: 16),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: ShadcnTheme.spacing1),
|
||||
ShadButton.ghost(
|
||||
onPressed: user.id != null ? () => _showDeleteDialog(user.id!, user.name) : null,
|
||||
child: const Icon(Icons.delete, size: 16),
|
||||
),
|
||||
],
|
||||
const SizedBox(width: ShadcnTheme.spacing1),
|
||||
ShadButton.ghost(
|
||||
onPressed: () => _showStatusChangeDialog(user),
|
||||
child: Icon(
|
||||
user.isActive ? Icons.person_off : Icons.person,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: ShadcnTheme.spacing1),
|
||||
ShadButton.ghost(
|
||||
onPressed: user.id != null ? () => _showDeleteDialog(user.id!, user.name) : null,
|
||||
child: const Icon(Icons.delete, size: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
flex: 0,
|
||||
useExpanded: false,
|
||||
minWidth: 120,
|
||||
),
|
||||
],
|
||||
);
|
||||
}).toList();
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPagination() {
|
||||
@@ -423,64 +536,3 @@ class _UserListState extends State<UserList> {
|
||||
// StandardDataRow 임시 정의
|
||||
}
|
||||
|
||||
/// 표준 데이터 행 위젯 (임시)
|
||||
class StandardDataRow extends StatelessWidget {
|
||||
final int index;
|
||||
final List<Widget> cells;
|
||||
final VoidCallback? onTap;
|
||||
final bool selected;
|
||||
|
||||
const StandardDataRow({
|
||||
super.key,
|
||||
required this.index,
|
||||
required this.cells,
|
||||
this.onTap,
|
||||
this.selected = false,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onTap: onTap,
|
||||
child: Container(
|
||||
height: 56,
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: ShadcnTheme.spacing4,
|
||||
vertical: ShadcnTheme.spacing3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: selected
|
||||
? ShadcnTheme.primaryLight.withValues(alpha: 0.1)
|
||||
: (index.isEven
|
||||
? ShadcnTheme.muted.withValues(alpha: 0.3)
|
||||
: null),
|
||||
border: const Border(
|
||||
bottom: BorderSide(color: ShadcnTheme.border, width: 1),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: _buildCellWidgets(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> _buildCellWidgets() {
|
||||
return cells.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final cell = entry.value;
|
||||
|
||||
// 마지막 셀이 아니면 오른쪽에 간격 추가
|
||||
if (index < cells.length - 1) {
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: ShadcnTheme.spacing2),
|
||||
child: cell,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return cell;
|
||||
}
|
||||
}).toList();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user