refactor: UI 화면 통합 및 불필요한 파일 정리
Some checks failed
Flutter Test & Quality Check / Build APK (push) Has been cancelled
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled

- 모든 *_redesign.dart 파일을 기본 화면 파일로 통합
- 백업용 컨트롤러 파일들 제거 (*_controller.backup.dart)
- 사용하지 않는 예제 및 테스트 파일 제거
- Clean Architecture 적용 후 남은 정리 작업 완료
- 테스트 코드 정리 및 구조 개선 준비

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-08-11 14:00:44 +09:00
parent 162fe08618
commit 1e6da44917
103 changed files with 1224 additions and 2976 deletions

View File

@@ -123,10 +123,10 @@ class EquipmentInFormController extends ChangeNotifier {
void _loadWarehouseLocations() async {
try {
DebugLogger.log('입고지 목록 API 로드 시작', tag: 'EQUIPMENT_IN');
final locations = await _warehouseService.getWarehouseLocations();
warehouseLocations = locations.map((e) => e.name).toList();
final response = await _warehouseService.getWarehouseLocations();
warehouseLocations = response.items.map((e) => e.name).toList();
// 이름-ID 매핑 저장
warehouseLocationMap = {for (var loc in locations) loc.name: loc.id};
warehouseLocationMap = {for (var loc in response.items) loc.name: loc.id};
DebugLogger.log('입고지 목록 로드 성공', tag: 'EQUIPMENT_IN', data: {
'count': warehouseLocations.length,
'locations': warehouseLocations,
@@ -146,8 +146,8 @@ class EquipmentInFormController extends ChangeNotifier {
void _loadPartnerCompanies() async {
try {
DebugLogger.log('파트너사 목록 API 로드 시작', tag: 'EQUIPMENT_IN');
final companies = await _companyService.getCompanies();
partnerCompanies = companies.map((c) => c.name).toList();
final response = await _companyService.getCompanies();
partnerCompanies = response.items.map((c) => c.name).toList();
DebugLogger.log('파트너사 목록 로드 성공', tag: 'EQUIPMENT_IN', data: {
'count': partnerCompanies.length,
'companies': partnerCompanies,

View File

@@ -1,281 +0,0 @@
import 'package:flutter/foundation.dart';
import 'package:get_it/get_it.dart';
import 'package:superport/models/equipment_unified_model.dart';
import 'package:superport/services/equipment_service.dart';
import 'package:superport/utils/constants.dart';
import 'package:superport/core/errors/failures.dart';
import 'package:superport/models/equipment_unified_model.dart' as legacy;
import 'package:superport/core/utils/debug_logger.dart';
// companyTypeToString 함수 import
import 'package:superport/utils/constants.dart'
show companyTypeToString, CompanyType;
import 'package:superport/models/company_model.dart';
import 'package:superport/models/address_model.dart';
import 'package:superport/core/utils/equipment_status_converter.dart';
// 장비 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러
class EquipmentListController extends ChangeNotifier {
final EquipmentService _equipmentService = GetIt.instance<EquipmentService>();
List<UnifiedEquipment> equipments = [];
String? selectedStatusFilter;
String searchKeyword = ''; // 검색어 추가
final Set<String> selectedEquipmentIds = {}; // 'id:status' 형식
bool _isLoading = false;
String? _error;
// API만 사용
// 페이지네이션
int _currentPage = 1;
final int _perPage = 20;
bool _hasMore = true;
// Getters
bool get isLoading => _isLoading;
String? get error => _error;
bool get hasMore => _hasMore;
int get currentPage => _currentPage;
EquipmentListController();
// 데이터 로드 및 상태 필터 적용
Future<void> loadData({bool isRefresh = false, String? search}) async {
if (_isLoading) return;
_isLoading = true;
_error = null;
notifyListeners();
try {
// API 호출 - 전체 데이터 로드
print('╔══════════════════════════════════════════════════════════');
print('║ 📦 장비 목록 API 호출 시작');
print('║ • 상태 필터: ${selectedStatusFilter ?? "전체"}');
print('║ • 검색어: ${search ?? searchKeyword}');
print('╚══════════════════════════════════════════════════════════');
// 전체 데이터를 가져오기 위해 큰 perPage 값 사용
final apiEquipmentDtos = await _equipmentService.getEquipmentsWithStatus(
page: 1,
perPage: 1000, // 충분히 큰 값으로 전체 데이터 로드
status: selectedStatusFilter != null ? EquipmentStatusConverter.clientToServer(selectedStatusFilter) : null,
search: search ?? searchKeyword,
);
print('╔══════════════════════════════════════════════════════════');
print('║ 📊 장비 목록 로드 완료');
print('║ ▶ 총 장비 수: ${apiEquipmentDtos.length}');
print('╟──────────────────────────────────────────────────────────');
// 상태별 통계
Map<String, int> statusCount = {};
for (final dto in apiEquipmentDtos) {
final clientStatus = EquipmentStatusConverter.serverToClient(dto.status);
statusCount[clientStatus] = (statusCount[clientStatus] ?? 0) + 1;
}
statusCount.forEach((status, count) {
print('║ • $status: $count개');
});
print('╟──────────────────────────────────────────────────────────');
print('║ 📑 전체 데이터 로드 완료');
print('║ • View에서 페이지네이션 처리 예정');
print('╚══════════════════════════════════════════════════════════');
// DTO를 UnifiedEquipment로 변환 (status 정보 포함)
final List<UnifiedEquipment> unifiedEquipments = apiEquipmentDtos.map((dto) {
final equipment = Equipment(
id: dto.id,
manufacturer: dto.manufacturer,
name: dto.modelName ?? dto.equipmentNumber,
category: '', // 세부 정보는 상세 조회에서 가져와야 함
subCategory: '',
subSubCategory: '',
serialNumber: dto.serialNumber,
quantity: 1,
inDate: dto.createdAt,
);
return UnifiedEquipment(
id: dto.id,
equipment: equipment,
date: dto.createdAt,
status: EquipmentStatusConverter.serverToClient(dto.status), // 서버 status를 클라이언트 status로 변환
);
}).toList();
equipments = unifiedEquipments;
_hasMore = false; // 전체 데이터를 로드했으므로 더 이상 로드할 필요 없음
selectedEquipmentIds.clear();
} on Failure catch (e) {
_error = e.message;
} catch (e) {
_error = 'An unexpected error occurred: $e';
} finally {
_isLoading = false;
notifyListeners();
}
}
// 상태 필터 변경
Future<void> changeStatusFilter(String? status) async {
selectedStatusFilter = status;
await loadData(isRefresh: true);
}
// 검색어 변경
Future<void> updateSearchKeyword(String keyword) async {
searchKeyword = keyword;
await loadData(isRefresh: true, search: keyword);
}
// 장비 선택/해제 (모든 상태 지원)
void selectEquipment(int? id, String status, bool? isSelected) {
if (id == null || isSelected == null) return;
final key = '$id:$status';
if (isSelected) {
selectedEquipmentIds.add(key);
} else {
selectedEquipmentIds.remove(key);
}
notifyListeners();
}
// 선택된 입고 장비 수 반환
int getSelectedInStockCount() {
int count = 0;
for (final idStatusPair in selectedEquipmentIds) {
final parts = idStatusPair.split(':');
if (parts.length == 2 && parts[1] == EquipmentStatus.in_) {
count++;
}
}
return count;
}
// 선택된 전체 장비 수 반환
int getSelectedEquipmentCount() {
return selectedEquipmentIds.length;
}
// 선택된 특정 상태의 장비 수 반환
int getSelectedEquipmentCountByStatus(String status) {
int count = 0;
for (final idStatusPair in selectedEquipmentIds) {
final parts = idStatusPair.split(':');
if (parts.length == 2 && parts[1] == status) {
count++;
}
}
return count;
}
// 선택된 장비들의 UnifiedEquipment 객체 목록 반환
List<UnifiedEquipment> getSelectedEquipments() {
List<UnifiedEquipment> selected = [];
for (final idStatusPair in selectedEquipmentIds) {
final parts = idStatusPair.split(':');
if (parts.length == 2) {
final id = int.tryParse(parts[0]);
if (id != null) {
final equipment = equipments.firstWhere(
(e) => e.id == id && e.status == parts[1],
orElse: () => null as UnifiedEquipment,
);
if (equipment != null) {
selected.add(equipment);
}
}
}
}
return selected;
}
// 선택된 특정 상태의 장비들의 UnifiedEquipment 객체 목록 반환
List<UnifiedEquipment> getSelectedEquipmentsByStatus(String status) {
List<UnifiedEquipment> selected = [];
for (final idStatusPair in selectedEquipmentIds) {
final parts = idStatusPair.split(':');
if (parts.length == 2 && parts[1] == status) {
final id = int.tryParse(parts[0]);
if (id != null) {
final equipment = equipments.firstWhere(
(e) => e.id == id && e.status == status,
orElse: () => null as UnifiedEquipment,
);
if (equipment != null) {
selected.add(equipment);
}
}
}
}
return selected;
}
// 선택된 장비들의 요약 정보를 Map 형태로 반환 (출고/대여/폐기 폼에서 사용)
List<Map<String, dynamic>> getSelectedEquipmentsSummary() {
List<Map<String, dynamic>> summaryList = [];
List<UnifiedEquipment> selectedEquipmentsInStock =
getSelectedEquipmentsByStatus(EquipmentStatus.in_);
for (final equipment in selectedEquipmentsInStock) {
summaryList.add({
'equipment': equipment.equipment,
'equipmentInId': equipment.id,
'status': equipment.status,
});
}
return summaryList;
}
// 출고 정보(회사, 담당자, 라이센스 등) 반환
// 출고 정보는 API를 통해 번별로 조회해야 하므로 별도 서비스로 분리 예정
String getOutEquipmentInfo(int equipmentId, String infoType) {
// TODO: API로 출고 정보 조회 구현
return '-';
}
// 장비 삭제
Future<bool> deleteEquipment(UnifiedEquipment equipment) async {
try {
// API를 통한 삭제
if (equipment.equipment.id != null) {
await _equipmentService.deleteEquipment(equipment.equipment.id!);
} else {
throw Exception('Equipment ID is null');
}
// 로컬 리스트에서도 제거
equipments.removeWhere((e) => e.id == equipment.id && e.status == equipment.status);
notifyListeners();
return true;
} on Failure catch (e) {
_error = e.message;
notifyListeners();
return false;
} catch (e) {
_error = 'Failed to delete equipment: $e';
notifyListeners();
return false;
}
}
// API만 사용하므로 토글 기능 제거
// 에러 처리
void clearError() {
_error = null;
notifyListeners();
}
@override
void dispose() {
super.dispose();
}
}

View File

@@ -79,7 +79,7 @@ class EquipmentListController extends BaseListController<UnifiedEquipment> {
}
// DTO를 UnifiedEquipment로 변환
final items = apiEquipmentDtos.map((dto) {
final items = apiEquipmentDtos.items.map((dto) {
final equipment = Equipment(
id: dto.id,
manufacturer: dto.manufacturer ?? 'Unknown',
@@ -109,7 +109,7 @@ class EquipmentListController extends BaseListController<UnifiedEquipment> {
perPage: params.perPage,
total: items.length < params.perPage ?
(params.page - 1) * params.perPage + items.length :
params.page * params.perPage + 1,
(params.page * params.perPage) + 1,
totalPages: items.length < params.perPage ? params.page : params.page + 1,
hasNext: items.length >= params.perPage,
hasPrevious: params.page > 1,

View File

@@ -79,8 +79,8 @@ class EquipmentOutFormController extends ChangeNotifier {
Future<void> loadDropdownData() async {
try {
// API를 통해 회사 목록 로드
final allCompanies = await _companyService.getCompanies();
companies = allCompanies
final response = await _companyService.getCompanies();
companies = response.items
.where((c) => c.companyTypes.contains(CompanyType.customer))
.map((c) => CompanyBranchInfo(
id: c.id,
@@ -204,9 +204,9 @@ class EquipmentOutFormController extends ChangeNotifier {
// 선택된 회사 정보에서 ID 추출
if (selectedCompanies[0] != null) {
final companies = await companyService.getCompanies(search: selectedCompanies[0]);
if (companies.isNotEmpty) {
companyId = companies.first.id;
final response = await companyService.getCompanies(search: selectedCompanies[0]);
if (response.items.isNotEmpty) {
companyId = response.items.first.id;
// TODO: 지점 ID 처리 로직 추가
}
}

View File

@@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
// import 'package:superport/models/equipment_unified_model.dart';
// import 'package:superport/screens/common/custom_widgets.dart' hide FormFieldWrapper;
import 'package:superport/screens/common/theme_tailwind.dart';
import 'package:superport/screens/common/theme_shadcn.dart';
import 'package:superport/screens/common/templates/form_layout_template.dart';
import 'package:superport/utils/constants.dart';
// import 'package:flutter_localizations/flutter_localizations.dart';
@@ -2163,7 +2163,7 @@ class _EquipmentInFormScreenState extends State<EquipmentInFormScreen> {
children: [
Text(
'${_controller.inDate.year}-${_controller.inDate.month.toString().padLeft(2, '0')}-${_controller.inDate.day.toString().padLeft(2, '0')}',
style: AppThemeTailwind.bodyStyle,
style: ShadcnTheme.bodyMedium,
),
const Icon(Icons.calendar_today, size: 20),
],
@@ -2258,7 +2258,7 @@ class _EquipmentInFormScreenState extends State<EquipmentInFormScreen> {
Expanded(
child: Text(
'${_controller.warrantyStartDate.year}-${_controller.warrantyStartDate.month.toString().padLeft(2, '0')}-${_controller.warrantyStartDate.day.toString().padLeft(2, '0')}',
style: AppThemeTailwind.bodyStyle,
style: ShadcnTheme.bodyMedium,
overflow: TextOverflow.ellipsis,
),
),
@@ -2308,7 +2308,7 @@ class _EquipmentInFormScreenState extends State<EquipmentInFormScreen> {
Expanded(
child: Text(
'${_controller.warrantyEndDate.year}-${_controller.warrantyEndDate.month.toString().padLeft(2, '0')}-${_controller.warrantyEndDate.day.toString().padLeft(2, '0')}',
style: AppThemeTailwind.bodyStyle,
style: ShadcnTheme.bodyMedium,
overflow: TextOverflow.ellipsis,
),
),

View File

@@ -1,484 +0,0 @@
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:provider/provider.dart';
import '../../services/lookup_service.dart';
import '../../data/models/lookups/lookup_data.dart';
import '../common/theme_shadcn.dart';
import '../common/components/shadcn_components.dart';
/// LookupService를 활용한 장비 입고 폼 예시
/// 전역 캐싱된 Lookup 데이터를 활용하여 드롭다운 구성
class EquipmentInFormLookupExample extends StatefulWidget {
const EquipmentInFormLookupExample({super.key});
@override
State<EquipmentInFormLookupExample> createState() => _EquipmentInFormLookupExampleState();
}
class _EquipmentInFormLookupExampleState extends State<EquipmentInFormLookupExample> {
late final LookupService _lookupService;
// 선택된 값들
String? _selectedEquipmentType;
String? _selectedEquipmentStatus;
String? _selectedManufacturer;
String? _selectedLicenseType;
// 텍스트 컨트롤러
final _serialNumberController = TextEditingController();
final _quantityController = TextEditingController();
final _descriptionController = TextEditingController();
@override
void initState() {
super.initState();
_lookupService = GetIt.instance<LookupService>();
_loadLookupDataIfNeeded();
}
/// 필요시 Lookup 데이터 로드 (캐시가 없을 경우)
Future<void> _loadLookupDataIfNeeded() async {
if (!_lookupService.hasData) {
await _lookupService.loadAllLookups();
if (mounted) {
setState(() {}); // UI 업데이트
}
}
}
@override
void dispose() {
_serialNumberController.dispose();
_quantityController.dispose();
_descriptionController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: ShadcnTheme.background,
appBar: AppBar(
title: const Text('장비 입고 (Lookup 활용 예시)'),
backgroundColor: ShadcnTheme.card,
elevation: 0,
),
body: ChangeNotifierProvider.value(
value: _lookupService,
child: Consumer<LookupService>(
builder: (context, lookupService, child) {
if (lookupService.isLoading) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (lookupService.error != null) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error_outline,
size: 64,
color: ShadcnTheme.destructive,
),
const SizedBox(height: 16),
Text(
'Lookup 데이터 로드 실패',
style: ShadcnTheme.headingH4,
),
const SizedBox(height: 8),
Text(
lookupService.error!,
style: ShadcnTheme.bodyMuted,
),
const SizedBox(height: 16),
ShadcnButton(
text: '다시 시도',
onPressed: () => lookupService.loadAllLookups(forceRefresh: true),
),
],
),
);
}
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Center(
child: Container(
constraints: const BoxConstraints(maxWidth: 800),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 안내 메시지
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: ShadcnTheme.primary.withValues(alpha: 0.1),
border: Border.all(
color: ShadcnTheme.primary.withValues(alpha: 0.3),
),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
Icon(Icons.info_outline,
color: ShadcnTheme.primary,
size: 20,
),
const SizedBox(width: 12),
Expanded(
child: Text(
'이 화면은 /lookups API를 통해 캐싱된 전역 데이터를 활용합니다.\n'
'드롭다운 데이터는 앱 시작 시 한 번만 로드되어 모든 화면에서 재사용됩니다.',
style: ShadcnTheme.bodySmall,
),
),
],
),
),
const SizedBox(height: 24),
// 폼 카드
ShadcnCard(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('장비 정보', style: ShadcnTheme.headingH4),
const SizedBox(height: 24),
// 장비 타입 드롭다운
_buildDropdownField(
label: '장비 타입',
value: _selectedEquipmentType,
items: lookupService.equipmentTypes,
onChanged: (value) {
setState(() {
_selectedEquipmentType = value;
});
},
),
// 장비 상태 드롭다운
_buildDropdownField(
label: '장비 상태',
value: _selectedEquipmentStatus,
items: lookupService.equipmentStatuses,
onChanged: (value) {
setState(() {
_selectedEquipmentStatus = value;
});
},
),
// 제조사 드롭다운
_buildDropdownField(
label: '제조사',
value: _selectedManufacturer,
items: lookupService.manufacturers,
onChanged: (value) {
setState(() {
_selectedManufacturer = value;
});
},
),
// 시리얼 번호 입력
_buildTextField(
label: '시리얼 번호',
controller: _serialNumberController,
hintText: 'SN-2025-001',
),
// 수량 입력
_buildTextField(
label: '수량',
controller: _quantityController,
hintText: '1',
keyboardType: TextInputType.number,
),
// 라이선스 타입 드롭다운 (옵션)
_buildDropdownField(
label: '라이선스 타입 (선택)',
value: _selectedLicenseType,
items: lookupService.licenseTypes,
onChanged: (value) {
setState(() {
_selectedLicenseType = value;
});
},
isOptional: true,
),
// 비고 입력
_buildTextField(
label: '비고',
controller: _descriptionController,
hintText: '추가 정보를 입력하세요',
maxLines: 3,
),
const SizedBox(height: 32),
// 버튼 그룹
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ShadcnButton(
text: '취소',
variant: ShadcnButtonVariant.secondary,
onPressed: () => Navigator.pop(context),
),
const SizedBox(width: 12),
ShadcnButton(
text: '저장',
onPressed: _handleSubmit,
),
],
),
],
),
),
const SizedBox(height: 24),
// 캐시 정보 표시
_buildCacheInfoCard(lookupService),
],
),
),
),
);
},
),
),
);
}
/// 드롭다운 필드 빌더
Widget _buildDropdownField({
required String label,
required String? value,
required List<LookupItem> items,
required ValueChanged<String?> onChanged,
bool isOptional = false,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(label, style: ShadcnTheme.bodyMedium),
if (isOptional) ...[
const SizedBox(width: 4),
Text('(선택)', style: ShadcnTheme.bodyMuted),
],
],
),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
border: Border.all(color: ShadcnTheme.border),
borderRadius: BorderRadius.circular(6),
),
child: DropdownButtonFormField<String>(
value: value,
decoration: InputDecoration(
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
border: InputBorder.none,
hintText: '선택하세요',
hintStyle: ShadcnTheme.bodyMuted,
),
items: items.map((item) => DropdownMenuItem(
value: item.code ?? '',
child: Text(item.name ?? ''),
)).toList(),
onChanged: onChanged,
),
),
const SizedBox(height: 16),
],
);
}
/// 텍스트 필드 빌더
Widget _buildTextField({
required String label,
required TextEditingController controller,
String? hintText,
TextInputType? keyboardType,
int maxLines = 1,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: ShadcnTheme.bodyMedium),
const SizedBox(height: 8),
TextFormField(
controller: controller,
keyboardType: keyboardType,
maxLines: maxLines,
decoration: InputDecoration(
hintText: hintText,
hintStyle: ShadcnTheme.bodyMuted,
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: BorderSide(color: ShadcnTheme.border),
),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: BorderSide(color: ShadcnTheme.border),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(6),
borderSide: BorderSide(color: ShadcnTheme.primary, width: 2),
),
contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
),
const SizedBox(height: 16),
],
);
}
/// 캐시 정보 카드
Widget _buildCacheInfoCard(LookupService lookupService) {
return ShadcnCard(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.storage, size: 20, color: ShadcnTheme.muted),
const SizedBox(width: 8),
Text('Lookup 캐시 정보', style: ShadcnTheme.bodyMedium),
],
),
const SizedBox(height: 12),
_buildCacheItem('장비 타입', lookupService.equipmentTypes.length),
_buildCacheItem('장비 상태', lookupService.equipmentStatuses.length),
_buildCacheItem('제조사', lookupService.manufacturers.length),
_buildCacheItem('라이선스 타입', lookupService.licenseTypes.length),
_buildCacheItem('사용자 역할', lookupService.userRoles.length),
_buildCacheItem('회사 상태', lookupService.companyStatuses.length),
const SizedBox(height: 12),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'캐시 상태: ${lookupService.isCacheValid ? "유효" : "만료"}',
style: ShadcnTheme.bodySmall.copyWith(
color: lookupService.isCacheValid
? ShadcnTheme.success
: ShadcnTheme.warning,
),
),
TextButton(
onPressed: () => lookupService.loadAllLookups(forceRefresh: true),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.refresh, size: 16, color: ShadcnTheme.primary),
const SizedBox(width: 4),
Text('캐시 새로고침',
style: TextStyle(color: ShadcnTheme.primary),
),
],
),
),
],
),
],
),
);
}
Widget _buildCacheItem(String label, int count) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(label, style: ShadcnTheme.bodySmall),
Text('$count개',
style: ShadcnTheme.bodySmall.copyWith(
color: ShadcnTheme.muted,
),
),
],
),
);
}
/// 폼 제출 처리
void _handleSubmit() {
// 유효성 검증
if (_selectedEquipmentType == null) {
_showSnackBar('장비 타입을 선택하세요', isError: true);
return;
}
if (_selectedEquipmentStatus == null) {
_showSnackBar('장비 상태를 선택하세요', isError: true);
return;
}
if (_serialNumberController.text.isEmpty) {
_showSnackBar('시리얼 번호를 입력하세요', isError: true);
return;
}
// 선택된 값 정보 표시
final selectedType = _lookupService.findByCode(
_lookupService.equipmentTypes,
_selectedEquipmentType!,
);
final selectedStatus = _lookupService.findByCode(
_lookupService.equipmentStatuses,
_selectedEquipmentStatus!,
);
final message = '''
장비 입고 정보:
- 타입: ${selectedType?.name ?? _selectedEquipmentType}
- 상태: ${selectedStatus?.name ?? _selectedEquipmentStatus}
- 시리얼: ${_serialNumberController.text}
- 수량: ${_quantityController.text.isEmpty ? "1" : _quantityController.text}
''';
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('입고 정보 확인'),
content: Text(message),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('확인'),
),
],
),
);
}
void _showSnackBar(String message, {bool isError = false}) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(message),
backgroundColor: isError ? ShadcnTheme.destructive : ShadcnTheme.primary,
duration: const Duration(seconds: 2),
),
);
}
}

View File

@@ -15,17 +15,17 @@ import 'package:superport/utils/equipment_display_helper.dart';
import 'package:superport/screens/equipment/widgets/equipment_history_dialog.dart';
/// shadcn/ui
class EquipmentListRedesign extends StatefulWidget {
class EquipmentList extends StatefulWidget {
final String currentRoute;
const EquipmentListRedesign({Key? key, this.currentRoute = Routes.equipment})
const EquipmentList({Key? key, this.currentRoute = Routes.equipment})
: super(key: key);
@override
State<EquipmentListRedesign> createState() => _EquipmentListRedesignState();
State<EquipmentList> createState() => _EquipmentListState();
}
class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
class _EquipmentListState extends State<EquipmentList> {
late final EquipmentListController _controller;
bool _showDetailedColumns = true;
final TextEditingController _searchController = TextEditingController();
@@ -34,14 +34,14 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
String _selectedStatus = 'all';
// String _searchKeyword = ''; // Removed - unused field
String _appliedSearchKeyword = '';
int _currentPage = 1;
final int _pageSize = 10;
// Controller에서
final Set<int> _selectedItems = {};
@override
void initState() {
super.initState();
_controller = EquipmentListController();
_controller.pageSize = 10; //
_setInitialFilter();
// API Future로
@@ -113,7 +113,7 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
} else if (status == 'rent') {
_controller.selectedStatusFilter = EquipmentStatus.rent;
}
_currentPage = 1;
_controller.goToPage(1);
});
_controller.changeStatusFilter(_controller.selectedStatusFilter);
}
@@ -122,7 +122,7 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
void _onSearch() async {
setState(() {
_appliedSearchKeyword = _searchController.text;
_currentPage = 1;
_controller.goToPage(1);
});
_controller.updateSearchKeyword(_searchController.text);
}
@@ -414,14 +414,12 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
dataTable: _buildDataTable(filteredEquipments),
//
pagination: totalCount > _pageSize ? Pagination(
pagination: totalCount > controller.pageSize ? Pagination(
totalCount: totalCount,
currentPage: _currentPage,
pageSize: _pageSize,
currentPage: controller.currentPage,
pageSize: controller.pageSize,
onPageChanged: (page) {
setState(() {
_currentPage = page;
});
controller.goToPage(page);
},
) : null,
);
@@ -515,7 +513,7 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
onRefresh: () {
setState(() {
_controller.loadData();
_currentPage = 1;
_controller.goToPage(1);
});
},
statusMessage:
@@ -548,7 +546,7 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
if (result == true) {
setState(() {
_controller.loadData();
_currentPage = 1;
_controller.goToPage(1);
});
}
},
@@ -623,7 +621,7 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
if (result == true) {
setState(() {
_controller.loadData();
_currentPage = 1;
_controller.goToPage(1);
});
}
},
@@ -829,7 +827,7 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
//
_buildDataCell(
Text(
'${((_currentPage - 1) * _pageSize) + index + 1}',
'${((_controller.currentPage - 1) * _controller.pageSize) + index + 1}',
style: ShadcnTheme.bodySmall,
),
flex: 1,
@@ -946,11 +944,11 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
///
Widget _buildDataTable(List<UnifiedEquipment> filteredEquipments) {
final int startIndex = (_currentPage - 1) * _pageSize;
final int startIndex = (_controller.currentPage - 1) * _controller.pageSize;
final int endIndex =
(startIndex + _pageSize) > filteredEquipments.length
(startIndex + _controller.pageSize) > filteredEquipments.length
? filteredEquipments.length
: (startIndex + _pageSize);
: (startIndex + _controller.pageSize);
final List<UnifiedEquipment> pagedEquipments = filteredEquipments.sublist(
startIndex,
endIndex,
@@ -975,7 +973,7 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
if (result == true) {
setState(() {
_controller.loadData();
_currentPage = 1;
_controller.goToPage(1);
});
}
},
@@ -1176,8 +1174,8 @@ class _EquipmentListRedesignState extends State<EquipmentListRedesign> {
///
List<UnifiedEquipment> _getPagedEquipments() {
final filteredEquipments = _getFilteredEquipments();
final int startIndex = (_currentPage - 1) * _pageSize;
final int endIndex = startIndex + _pageSize;
final int startIndex = (_controller.currentPage - 1) * _controller.pageSize;
final int endIndex = startIndex + _controller.pageSize;
if (startIndex >= filteredEquipments.length) {
return [];

View File

@@ -6,7 +6,7 @@ import 'package:superport/models/company_model.dart';
import 'package:superport/models/company_branch_info.dart';
import 'package:superport/models/address_model.dart';
import 'package:superport/screens/common/custom_widgets.dart';
import 'package:superport/screens/common/theme_tailwind.dart';
import 'package:superport/screens/common/theme_shadcn.dart';
import 'package:superport/screens/equipment/controllers/equipment_out_form_controller.dart';
import 'package:superport/screens/equipment/widgets/equipment_summary_card.dart';
import 'package:superport/screens/equipment/widgets/equipment_summary_row.dart';
@@ -421,7 +421,10 @@ class _EquipmentOutFormScreenState extends State<EquipmentOutFormScreen> {
: null,
style:
canSubmit
? AppThemeTailwind.primaryButtonStyle
? ElevatedButton.styleFrom(
backgroundColor: ShadcnTheme.primary,
foregroundColor: Colors.white,
)
: ElevatedButton.styleFrom(
backgroundColor: Colors.grey.shade300,
foregroundColor: Colors.grey.shade700,
@@ -600,7 +603,7 @@ class _EquipmentOutFormScreenState extends State<EquipmentOutFormScreen> {
company.contactName!.isNotEmpty)
Text(
'${company.contactName} ${company.contactPosition ?? ""} ${company.contactPhone ?? ""} ${company.contactEmail ?? ""}',
style: AppThemeTailwind.bodyStyle,
style: ShadcnTheme.bodyMedium,
),
if (!companyInfo.isMainCompany &&
branch != null &&
@@ -608,7 +611,7 @@ class _EquipmentOutFormScreenState extends State<EquipmentOutFormScreen> {
branch.contactName!.isNotEmpty)
Text(
'${branch.contactName} ${branch.contactPosition ?? ""} ${branch.contactPhone ?? ""} ${branch.contactEmail ?? ""}',
style: AppThemeTailwind.bodyStyle,
style: ShadcnTheme.bodyMedium,
),
const SizedBox(height: 8),
// 담당자 목록에서 실제 담당자 정보만 표시하는 부분은 제거
@@ -686,7 +689,7 @@ class _EquipmentOutFormScreenState extends State<EquipmentOutFormScreen> {
children: [
Text(
controller.formatDate(date),
style: AppThemeTailwind.bodyStyle,
style: ShadcnTheme.bodyMedium,
),
const Icon(Icons.calendar_today, size: 20),
],
@@ -817,7 +820,7 @@ class _EquipmentOutFormScreenState extends State<EquipmentOutFormScreen> {
userWidgets.add(
Text(
'정수진 사원 010-4567-8901 jung.soojin@lg.com',
style: AppThemeTailwind.bodyStyle,
style: ShadcnTheme.bodyMedium,
),
);
}