refactor: 코드베이스 정리 및 에러 처리 개선

- API 클라이언트 및 인증 인터셉터 에러 처리 강화
- 의존성 주입 실패 시에도 앱 실행 가능하도록 개선
- 사용하지 않는 레거시 UI 컴포넌트 및 화면 제거
- pubspec.yaml 의존성 업데이트

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-07-25 18:15:21 +09:00
parent 71b7b7f40b
commit ad2c699ff7
39 changed files with 193 additions and 4134 deletions

View File

@@ -1,501 +0,0 @@
import 'package:flutter/material.dart';
import 'package:superport/models/company_model.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
import 'package:superport/screens/common/main_layout.dart';
import 'package:superport/screens/common/custom_widgets.dart';
import 'package:superport/services/mock_data_service.dart';
import 'package:superport/utils/constants.dart';
import 'package:superport/screens/common/widgets/pagination.dart';
import 'package:superport/screens/company/widgets/company_branch_dialog.dart';
class CompanyListScreen extends StatefulWidget {
const CompanyListScreen({super.key});
@override
State<CompanyListScreen> createState() => _CompanyListScreenState();
}
class _CompanyListScreenState extends State<CompanyListScreen> {
final MockDataService _dataService = MockDataService();
List<Company> _companies = [];
// 페이지네이션 상태 추가
int _currentPage = 1; // 현재 페이지 (1부터 시작)
final int _pageSize = 10; // 페이지당 개수
@override
void initState() {
super.initState();
_loadData();
}
void _loadData() {
setState(() {
_companies = _dataService.getAllCompanies();
// 데이터가 변경되면 첫 페이지로 이동
_currentPage = 1;
});
}
void _navigateToAddScreen() async {
final result = await Navigator.pushNamed(context, '/company/add');
if (result == true) {
_loadData();
}
}
void _navigateToEditScreen(int id) async {
final result = await Navigator.pushNamed(
context,
'/company/edit',
arguments: id,
);
if (result == true) {
_loadData();
}
}
void _deleteCompany(int id) {
showDialog(
context: context,
builder:
(context) => AlertDialog(
title: const Text('삭제 확인'),
content: const Text('이 회사 정보를 삭제하시겠습니까?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('취소'),
),
TextButton(
onPressed: () {
_dataService.deleteCompany(id);
Navigator.pop(context);
_loadData();
},
child: const Text('삭제'),
),
],
),
);
}
// 회사 유형에 따라 칩 위젯 생성 (복수)
Widget _buildCompanyTypeChips(List<CompanyType> types) {
return Row(
children:
types.map((type) {
final Color textColor =
type == CompanyType.customer
? Colors.blue.shade800
: Colors.green.shade800;
final String label = companyTypeToString(type);
return Container(
margin: const EdgeInsets.only(right: 4),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
decoration: BoxDecoration(
color: textColor.withOpacity(0.1),
borderRadius: BorderRadius.circular(8),
),
child: Text(
label,
style: TextStyle(
color: textColor,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
);
}).toList(),
);
}
// 본사/지점 구분 표시 위젯
Widget _buildCompanyTypeLabel(bool isBranch, {String? mainCompanyName}) {
if (isBranch) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.account_tree, size: 16, color: Colors.blue.shade600),
const SizedBox(width: 4),
const Text('지점'),
],
);
} else {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.business, size: 16, color: Colors.grey.shade700),
const SizedBox(width: 4),
const Text('본사'),
],
);
}
}
// 회사 이름 표시 위젯 (지점인 경우 "본사명 > 지점명" 형식)
Widget _buildCompanyNameText(
Company company,
bool isBranch, {
String? mainCompanyName,
}) {
if (isBranch && mainCompanyName != null) {
return Text.rich(
TextSpan(
children: [
TextSpan(
text: isBranch ? '' : '',
style: TextStyle(color: Colors.grey.shade600, fontSize: 14),
),
TextSpan(
text: isBranch ? '$mainCompanyName > ' : '',
style: TextStyle(
color: Colors.grey.shade700,
fontWeight: FontWeight.normal,
),
),
TextSpan(
text: company.name,
style: const TextStyle(fontWeight: FontWeight.bold),
),
],
),
);
} else {
return Text(
company.name,
style: const TextStyle(fontWeight: FontWeight.bold),
);
}
}
// 지점(본사+지점)만 보여주는 팝업 오픈 함수
void _showBranchDialog(Company mainCompany) {
showDialog(
context: context,
builder: (context) => CompanyBranchDialog(mainCompany: mainCompany),
);
}
@override
Widget build(BuildContext context) {
// 대시보드 폭에 맞게 조정
final screenWidth = MediaQuery.of(context).size.width;
final maxContentWidth = screenWidth > 1200 ? 1200.0 : screenWidth - 32;
// 본사와 지점 구분하기 위한 데이터 준비
final List<Map<String, dynamic>> displayCompanies = [];
for (final company in _companies) {
displayCompanies.add({
'company': company,
'isBranch': false,
'mainCompanyName': null,
});
if (company.branches != null) {
for (final branch in company.branches!) {
displayCompanies.add({
'branch': branch, // 지점 객체 자체 저장
'companyId': company.id, // 본사 id 저장
'isBranch': true,
'mainCompanyName': company.name,
});
}
}
}
// 페이지네이션 데이터 슬라이싱
final int totalCount = displayCompanies.length;
final int startIndex = (_currentPage - 1) * _pageSize;
final int endIndex =
(startIndex + _pageSize) > totalCount
? totalCount
: (startIndex + _pageSize);
final List<Map<String, dynamic>> pagedCompanies = displayCompanies.sublist(
startIndex,
endIndex,
);
return MainLayout(
title: '회사 관리',
currentRoute: Routes.company,
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: _loadData,
color: Colors.grey,
),
],
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
PageTitle(
title: '회사 목록',
width: maxContentWidth - 32,
rightWidget: ElevatedButton.icon(
onPressed: _navigateToAddScreen,
icon: const Icon(Icons.add),
label: const Text('추가'),
style: AppThemeTailwind.primaryButtonStyle,
),
),
Expanded(
child: DataTableCard(
width: maxContentWidth - 32,
child:
pagedCompanies.isEmpty
? const Center(child: Text('등록된 회사 정보가 없습니다.'))
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
width: maxContentWidth - 32,
constraints: BoxConstraints(
minWidth: maxContentWidth - 64,
),
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: DataTable(
columns: const [
DataColumn(label: Text('번호')),
DataColumn(label: Text('구분')),
DataColumn(label: Text('회사명')),
DataColumn(label: Text('유형')),
DataColumn(label: Text('주소')),
DataColumn(label: Text('지점 수 (본사만 표시)')),
DataColumn(label: Text('관리')),
],
rows:
pagedCompanies.asMap().entries.map((entry) {
final index = entry.key;
final data = entry.value;
final bool isBranch =
data['isBranch'] as bool;
final String? mainCompanyName =
data['mainCompanyName'] as String?;
if (isBranch) {
final Branch branch =
data['branch'] as Branch;
final int companyId =
data['companyId'] as int;
return DataRow(
cells: [
DataCell(
Text('${startIndex + index + 1}'),
),
DataCell(
_buildCompanyTypeLabel(
true,
mainCompanyName:
mainCompanyName,
),
),
DataCell(
_buildCompanyNameText(
Company(
id: branch.id,
name: branch.name,
address: branch.address,
contactName:
branch.contactName,
contactPosition:
branch.contactPosition,
contactPhone:
branch.contactPhone,
contactEmail:
branch.contactEmail,
companyTypes: [],
remark: branch.remark,
),
true,
mainCompanyName:
mainCompanyName,
),
),
DataCell(
_buildCompanyTypeChips([]),
),
DataCell(
Text(branch.address.toString()),
),
DataCell(const Text('')),
DataCell(
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(
Icons.edit,
color:
AppThemeTailwind
.primary,
),
onPressed: () {
Navigator.pushNamed(
context,
'/company/edit',
arguments: {
'companyId':
companyId,
'isBranch': true,
'mainCompanyName':
mainCompanyName,
'branchId': branch.id,
},
).then((result) {
if (result == true)
_loadData();
});
},
),
IconButton(
icon: const Icon(
Icons.delete,
color:
AppThemeTailwind
.danger,
),
onPressed: () {
// 지점 삭제 로직 필요시 구현
},
),
],
),
),
],
);
} else {
final Company company =
data['company'] as Company;
return DataRow(
cells: [
DataCell(
Text('${startIndex + index + 1}'),
),
DataCell(
_buildCompanyTypeLabel(false),
),
DataCell(
_buildCompanyNameText(
company,
false,
),
),
DataCell(
_buildCompanyTypeChips(
company.companyTypes,
),
),
DataCell(
Text(company.address.toString()),
),
DataCell(
GestureDetector(
onTap: () {
if ((company
.branches
?.isNotEmpty ??
false)) {
_showBranchDialog(company);
}
},
child: MouseRegion(
cursor:
SystemMouseCursors.click,
child: Text(
'${(company.branches?.length ?? 0)}',
style: TextStyle(
color:
(company
.branches
?.isNotEmpty ??
false)
? Colors.blue
: Colors.black,
decoration:
(company
.branches
?.isNotEmpty ??
false)
? TextDecoration
.underline
: TextDecoration
.none,
),
),
),
),
),
DataCell(
Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(
Icons.edit,
color:
AppThemeTailwind
.primary,
),
onPressed: () {
Navigator.pushNamed(
context,
'/company/edit',
arguments: {
'companyId':
company.id,
'isBranch': false,
},
).then((result) {
if (result == true)
_loadData();
});
},
),
IconButton(
icon: const Icon(
Icons.delete,
color:
AppThemeTailwind
.danger,
),
onPressed: () {
_deleteCompany(
company.id!,
);
},
),
],
),
),
],
);
}
}).toList(),
),
),
),
),
),
),
// 페이지네이션 위젯 추가
if (totalCount > _pageSize)
Padding(
padding: const EdgeInsets.symmetric(vertical: 12),
child: Pagination(
totalCount: totalCount,
currentPage: _currentPage,
pageSize: _pageSize,
onPageChanged: (page) {
setState(() {
_currentPage = page;
});
},
),
),
],
),
),
);
}
}

View File

@@ -5,7 +5,7 @@ import 'package:pdf/widgets.dart' as pw; // PDF 생성용
import 'package:printing/printing.dart'; // PDF 프린트/미리보기용
import 'dart:typed_data'; // Uint8List
import 'package:pdf/pdf.dart'; // PdfColors, PageFormat 등 전체 임포트
import 'package:superport/screens/common/custom_widgets.dart'; // DataTableCard 사용을 위한 import
import 'package:superport/screens/common/components/shadcn_components.dart'; // ShadcnCard 사용을 위한 import
import 'package:flutter/services.dart'; // rootBundle 사용을 위한 import
/// 본사와 지점 리스트를 보여주는 다이얼로그 위젯
@@ -264,8 +264,7 @@ class CompanyBranchDialog extends StatelessWidget {
),
const SizedBox(height: 16),
Expanded(
child: DataTableCard(
width: maxDialogWidth - 48,
child: ShadcnCard(
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(