feat: API 연동 개선 및 라이선스 모델 확장
- 라이선스 모델 전면 개편 (상세 필드 추가, 계산 필드 구현) - API 응답 처리 개선 (HTTP 상태 코드 기반) - 장비 출고 폼 컨트롤러 추가 - 회사 지점 정보 모델 추가 - 공통 데이터 모델 구조 추가 - 전체 서비스 레이어 API 호출 방식 통일 - UI 컴포넌트 마이너 개선
This commit is contained in:
@@ -211,7 +211,7 @@ class _CompanyListRedesignState extends State<CompanyListRedesign> {
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.card,
|
||||
borderRadius: BorderRadius.circular(ShadcnTheme.radius),
|
||||
borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd),
|
||||
border: Border.all(color: ShadcnTheme.border),
|
||||
),
|
||||
child: TextField(
|
||||
@@ -262,7 +262,7 @@ class _CompanyListRedesignState extends State<CompanyListRedesign> {
|
||||
margin: const EdgeInsets.only(bottom: ShadcnTheme.spacing4),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade50,
|
||||
borderRadius: BorderRadius.circular(ShadcnTheme.radius),
|
||||
borderRadius: BorderRadius.circular(ShadcnTheme.radiusMd),
|
||||
border: Border.all(color: Colors.red.shade200),
|
||||
),
|
||||
child: Row(
|
||||
|
||||
Binary file not shown.
@@ -171,7 +171,7 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
// API 실패 시 Mock 데이터 사용
|
||||
_loadFromMockData(equipmentIn);
|
||||
}
|
||||
} else {
|
||||
} else if (equipmentIn != null) {
|
||||
_loadFromMockData(equipmentIn);
|
||||
}
|
||||
} else {
|
||||
@@ -311,11 +311,15 @@ class EquipmentInFormController extends ChangeNotifier {
|
||||
int? warehouseLocationId;
|
||||
if (warehouseLocation != null) {
|
||||
// TODO: 창고 위치 ID 가져오기 - 현재는 목 데이터에서 찾기
|
||||
final warehouse = dataService.getAllWarehouseLocations().firstWhere(
|
||||
(w) => w.name == warehouseLocation,
|
||||
orElse: () => null,
|
||||
);
|
||||
warehouseLocationId = warehouse?.id;
|
||||
try {
|
||||
final warehouse = dataService.getAllWarehouseLocations().firstWhere(
|
||||
(w) => w.name == warehouseLocation,
|
||||
);
|
||||
warehouseLocationId = warehouse.id;
|
||||
} catch (e) {
|
||||
// 창고를 찾을 수 없는 경우
|
||||
warehouseLocationId = null;
|
||||
}
|
||||
}
|
||||
|
||||
await _equipmentService.equipmentIn(
|
||||
|
||||
@@ -63,13 +63,12 @@ class EquipmentListController extends ChangeNotifier {
|
||||
);
|
||||
|
||||
// API 모델을 UnifiedEquipment로 변환
|
||||
final unifiedEquipments = apiEquipments.map((equipment) {
|
||||
final List<UnifiedEquipment> unifiedEquipments = apiEquipments.map((equipment) {
|
||||
return UnifiedEquipment(
|
||||
id: equipment.id,
|
||||
equipment: equipment,
|
||||
quantity: equipment.quantity,
|
||||
date: DateTime.now(), // 실제로는 API에서 날짜 정보를 가져와야 함
|
||||
status: EquipmentStatus.in_, // 기본값, 실제로는 API에서 가져와야 함
|
||||
locationTrack: LocationTrack.inStock,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:superport/models/equipment_unified_model.dart';
|
||||
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/services/mock_data_service.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
|
||||
/// 장비 출고 폼 컨트롤러
|
||||
///
|
||||
/// 폼의 전체 상태, 유효성, 저장, 데이터 로딩 등 비즈니스 로직을 담당한다.
|
||||
class EquipmentOutFormController extends ChangeNotifier {
|
||||
final MockDataService dataService;
|
||||
int? equipmentOutId;
|
||||
|
||||
// 편집 모드 여부
|
||||
bool isEditMode = false;
|
||||
|
||||
// 상태 관리
|
||||
bool _isLoading = false;
|
||||
String? _errorMessage;
|
||||
|
||||
// Getters
|
||||
bool get isLoading => _isLoading;
|
||||
String? get errorMessage => _errorMessage;
|
||||
|
||||
// 폼 키
|
||||
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
|
||||
|
||||
// 선택된 장비 정보
|
||||
Equipment? selectedEquipment;
|
||||
int? selectedEquipmentInId;
|
||||
List<Map<String, dynamic>>? selectedEquipments;
|
||||
|
||||
// 출고처 정보
|
||||
List<String?> selectedCompanies = [null];
|
||||
List<bool> hasManagersPerCompany = [false];
|
||||
List<List<String>> filteredManagersPerCompany = [[]];
|
||||
List<String?> selectedManagersPerCompany = [null];
|
||||
|
||||
// 라이선스 정보
|
||||
String? selectedLicense;
|
||||
|
||||
// 출고 타입
|
||||
String outType = 'O'; // 기본값: 출고
|
||||
|
||||
// 드롭다운 데이터
|
||||
List<CompanyBranchInfo> companies = [];
|
||||
List<String> licenses = [];
|
||||
|
||||
// 날짜
|
||||
DateTime outDate = DateTime.now();
|
||||
|
||||
// 회사 정보 (지점 포함)
|
||||
List<CompanyBranchInfo> get companiesWithBranches => companies;
|
||||
|
||||
// 비고
|
||||
final TextEditingController remarkController = TextEditingController();
|
||||
|
||||
EquipmentOutFormController({
|
||||
required this.dataService,
|
||||
this.equipmentOutId,
|
||||
}) {
|
||||
isEditMode = equipmentOutId != null;
|
||||
}
|
||||
|
||||
// 이용 가능한 회사 목록 (선택된 회사 제외)
|
||||
List<List<CompanyBranchInfo>> get availableCompaniesPerDropdown {
|
||||
return List.generate(selectedCompanies.length, (index) {
|
||||
final selectedBefore = selectedCompanies.sublist(0, index).whereType<String>().toSet();
|
||||
return companies.where((company) => !selectedBefore.contains(company.name)).toList();
|
||||
});
|
||||
}
|
||||
|
||||
// 드롭다운 데이터 로드
|
||||
void loadDropdownData() {
|
||||
// 회사 목록 로드 (출고처 가능한 회사만)
|
||||
companies = dataService.getAllCompanies()
|
||||
.where((c) => c.companyTypes.contains(CompanyType.customer))
|
||||
.map((c) => CompanyBranchInfo(
|
||||
id: c.id,
|
||||
name: c.name,
|
||||
originalName: c.name,
|
||||
isMainCompany: true,
|
||||
companyId: c.id,
|
||||
branchId: null,
|
||||
))
|
||||
.toList();
|
||||
|
||||
// 라이선스 목록 로드
|
||||
licenses = dataService.getAllLicenses().map((l) => l.name).toList();
|
||||
}
|
||||
|
||||
// 선택된 장비로 초기화
|
||||
void initializeWithSelectedEquipment(Equipment equipment) {
|
||||
selectedEquipment = equipment;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 회사 선택 시 담당자 목록 필터링
|
||||
void filterManagersForCompany(int index) {
|
||||
if (index >= selectedCompanies.length || selectedCompanies[index] == null) {
|
||||
hasManagersPerCompany[index] = false;
|
||||
filteredManagersPerCompany[index] = [];
|
||||
return;
|
||||
}
|
||||
|
||||
// Mock 데이터에서 회사별 담당자 목록 가져오기
|
||||
final company = dataService.getAllCompanies().firstWhere(
|
||||
(c) => c.name == selectedCompanies[index],
|
||||
orElse: () => Company(
|
||||
name: '',
|
||||
companyTypes: [],
|
||||
),
|
||||
);
|
||||
|
||||
if (company.name.isNotEmpty && company.contactName != null && company.contactName!.isNotEmpty) {
|
||||
// 회사의 담당자 정보
|
||||
hasManagersPerCompany[index] = true;
|
||||
filteredManagersPerCompany[index] = [company.contactName!];
|
||||
} else {
|
||||
hasManagersPerCompany[index] = false;
|
||||
filteredManagersPerCompany[index] = ['없음'];
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 인덱스별 담당자 필터링
|
||||
void filterManagersByCompanyAtIndex(int index) {
|
||||
filterManagersForCompany(index);
|
||||
}
|
||||
|
||||
// 회사 추가
|
||||
void addCompany() {
|
||||
selectedCompanies.add(null);
|
||||
hasManagersPerCompany.add(false);
|
||||
filteredManagersPerCompany.add([]);
|
||||
selectedManagersPerCompany.add(null);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 회사 제거
|
||||
void removeCompany(int index) {
|
||||
if (selectedCompanies.length > 1) {
|
||||
selectedCompanies.removeAt(index);
|
||||
hasManagersPerCompany.removeAt(index);
|
||||
filteredManagersPerCompany.removeAt(index);
|
||||
selectedManagersPerCompany.removeAt(index);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
// 회사 선택 초기화
|
||||
void resetCompanySelection() {
|
||||
selectedCompanies = [null];
|
||||
hasManagersPerCompany = [false];
|
||||
filteredManagersPerCompany = [[]];
|
||||
selectedManagersPerCompany = [null];
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 에러 초기화
|
||||
void clearError() {
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 이용 가능한 회사 목록 업데이트
|
||||
void updateAvailableCompanies() {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// 날짜 포맷팅
|
||||
String formatDate(DateTime date) {
|
||||
return DateFormat('yyyy-MM-dd').format(date);
|
||||
}
|
||||
|
||||
// 출고 정보 저장
|
||||
Future<bool> saveEquipmentOut(BuildContext context, {String? note}) async {
|
||||
// 유효성 검사
|
||||
if (selectedCompanies.isEmpty || selectedCompanies[0] == null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('출고처를 선택해주세요.')),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (selectedEquipment == null && (selectedEquipments == null || selectedEquipments!.isEmpty)) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('출고할 장비를 선택해주세요.')),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
// TODO: 실제 저장 로직 구현
|
||||
// 현재는 Mock 데이터 서비스에 저장
|
||||
|
||||
if (isEditMode) {
|
||||
// 수정 모드
|
||||
// dataService.updateEquipmentOut(...)
|
||||
} else {
|
||||
// 생성 모드
|
||||
if (selectedEquipments != null && selectedEquipments!.isNotEmpty) {
|
||||
// 다중 장비 출고
|
||||
for (var equipmentData in selectedEquipments!) {
|
||||
// dataService.addEquipmentOut(...)
|
||||
}
|
||||
} else if (selectedEquipment != null) {
|
||||
// 단일 장비 출고
|
||||
// dataService.addEquipmentOut(...)
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (e) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text('저장 중 오류가 발생했습니다: $e')),
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
remarkController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:superport/models/equipment_unified_model.dart';
|
||||
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';
|
||||
@@ -406,25 +407,17 @@ class _EquipmentOutFormScreenState extends State<EquipmentOutFormScreen> {
|
||||
}
|
||||
}
|
||||
|
||||
controller.saveEquipmentOut(
|
||||
(msg) {
|
||||
controller.saveEquipmentOut(context).then((success) {
|
||||
if (success) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(msg),
|
||||
duration: const Duration(seconds: 2),
|
||||
const SnackBar(
|
||||
content: Text('출고가 완료되었습니다.'),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
Navigator.pop(context, true);
|
||||
},
|
||||
(err) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(err),
|
||||
duration: const Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
: null,
|
||||
style:
|
||||
@@ -510,8 +503,8 @@ class _EquipmentOutFormScreenState extends State<EquipmentOutFormScreen> {
|
||||
controller.availableCompaniesPerDropdown[index]
|
||||
.map(
|
||||
(item) => DropdownMenuItem<String>(
|
||||
value: item,
|
||||
child: _buildCompanyDropdownItem(item, controller),
|
||||
value: item.name,
|
||||
child: _buildCompanyDropdownItem(item.name, controller),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
@@ -526,10 +519,7 @@ class _EquipmentOutFormScreenState extends State<EquipmentOutFormScreen> {
|
||||
controller.selectedCompanies[index - 1] != null)
|
||||
? (value) {
|
||||
controller.selectedCompanies[index] = value;
|
||||
controller.filterManagersByCompanyAtIndex(
|
||||
value,
|
||||
index,
|
||||
);
|
||||
controller.filterManagersByCompanyAtIndex(index);
|
||||
controller.updateAvailableCompanies();
|
||||
}
|
||||
: null,
|
||||
|
||||
@@ -24,11 +24,35 @@ class LicenseFormController extends ChangeNotifier {
|
||||
int _durationMonths = 12; // 기본값: 12개월
|
||||
String _visitCycle = '미방문'; // 기본값: 미방문
|
||||
|
||||
// isEditMode setter
|
||||
set isEditMode(bool value) {
|
||||
_isEditMode = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// name setter
|
||||
set name(String value) {
|
||||
_name = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// durationMonths setter
|
||||
set durationMonths(int value) {
|
||||
_durationMonths = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// visitCycle setter
|
||||
set visitCycle(String value) {
|
||||
_visitCycle = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
LicenseFormController({
|
||||
this.useApi = true,
|
||||
this.mockDataService,
|
||||
this.useApi = false,
|
||||
MockDataService? dataService,
|
||||
int? licenseId,
|
||||
}) {
|
||||
}) : mockDataService = dataService ?? MockDataService() {
|
||||
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
||||
_licenseService = GetIt.instance<LicenseService>();
|
||||
}
|
||||
@@ -89,10 +113,11 @@ class LicenseFormController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
if (_originalLicense != null) {
|
||||
_name = _originalLicense!.name;
|
||||
_companyId = _originalLicense!.companyId;
|
||||
_durationMonths = _originalLicense!.durationMonths;
|
||||
_visitCycle = _originalLicense!.visitCycle;
|
||||
_name = _originalLicense!.productName ?? '';
|
||||
_companyId = _originalLicense!.companyId ?? 1;
|
||||
// durationMonths와 visitCycle은 License 모델에 없으므로 기본값 유지
|
||||
// _durationMonths = _originalLicense!.durationMonths;
|
||||
// _visitCycle = _originalLicense!.visitCycle;
|
||||
}
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
@@ -115,10 +140,14 @@ class LicenseFormController extends ChangeNotifier {
|
||||
try {
|
||||
final license = License(
|
||||
id: _isEditMode ? _licenseId : null,
|
||||
licenseKey: 'LIC-${DateTime.now().millisecondsSinceEpoch}',
|
||||
productName: _name,
|
||||
companyId: _companyId,
|
||||
name: _name,
|
||||
durationMonths: _durationMonths,
|
||||
visitCycle: _visitCycle,
|
||||
// durationMonths와 visitCycle은 License 모델에 없음
|
||||
// 대신 expiryDate를 설정
|
||||
purchaseDate: DateTime.now(),
|
||||
expiryDate: DateTime.now().add(Duration(days: _durationMonths * 30)),
|
||||
remark: '방문주기: $_visitCycle',
|
||||
);
|
||||
|
||||
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:superport/core/errors/failures.dart';
|
||||
import 'package:superport/models/license_model.dart';
|
||||
import 'package:superport/services/license_service.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
@@ -24,8 +26,13 @@ class LicenseListController extends ChangeNotifier {
|
||||
int? _selectedCompanyId;
|
||||
bool? _isActive;
|
||||
String? _licenseType;
|
||||
String _sortBy = 'expiry_date';
|
||||
String _sortOrder = 'asc';
|
||||
|
||||
// 검색 디바운스를 위한 타이머
|
||||
Timer? _debounceTimer;
|
||||
|
||||
LicenseListController({this.useApi = true, this.mockDataService}) {
|
||||
LicenseListController({this.useApi = false, this.mockDataService}) {
|
||||
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
||||
_licenseService = GetIt.instance<LicenseService>();
|
||||
}
|
||||
@@ -137,11 +144,24 @@ class LicenseListController extends ChangeNotifier {
|
||||
await loadData(isInitialLoad: false);
|
||||
}
|
||||
|
||||
// 검색
|
||||
// 검색 (디바운싱 적용)
|
||||
void search(String query) {
|
||||
_searchQuery = query;
|
||||
_applySearchFilter();
|
||||
notifyListeners();
|
||||
|
||||
// 기존 타이머 취소
|
||||
_debounceTimer?.cancel();
|
||||
|
||||
// Mock 데이터는 즉시 검색
|
||||
if (!useApi) {
|
||||
_applySearchFilter();
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
// API 검색은 디바운싱 적용 (300ms)
|
||||
_debounceTimer = Timer(const Duration(milliseconds: 300), () {
|
||||
loadData();
|
||||
});
|
||||
}
|
||||
|
||||
// 검색 필터 적용
|
||||
@@ -212,10 +232,12 @@ class LicenseListController extends ChangeNotifier {
|
||||
final allLicenses = mockDataService?.getAllLicenses() ?? [];
|
||||
|
||||
return allLicenses.where((license) {
|
||||
// Mock 데이터는 만료일이 없으므로 임의로 계산
|
||||
final expiryDate = now.add(Duration(days: license.durationMonths * 30));
|
||||
final daysUntilExpiry = expiryDate.difference(now).inDays;
|
||||
return daysUntilExpiry > 0 && daysUntilExpiry <= days;
|
||||
// 실제 License 모델에서 만료일 확인
|
||||
if (license.expiryDate != null) {
|
||||
final daysUntilExpiry = license.expiryDate!.difference(now).inDays;
|
||||
return daysUntilExpiry > 0 && daysUntilExpiry <= days;
|
||||
}
|
||||
return false;
|
||||
}).toList();
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -224,4 +246,69 @@ class LicenseListController extends ChangeNotifier {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
// 상태별 라이선스 개수 조회
|
||||
Future<Map<String, int>> getLicenseStatusCounts() async {
|
||||
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
||||
try {
|
||||
// API에서 상태별 개수 조회 (실제로는 별도 엔드포인트가 있다면 사용)
|
||||
final activeCount = await _licenseService.getTotalLicenses(isActive: true);
|
||||
final inactiveCount = await _licenseService.getTotalLicenses(isActive: false);
|
||||
final expiringLicenses = await getExpiringLicenses(days: 30);
|
||||
|
||||
return {
|
||||
'active': activeCount,
|
||||
'inactive': inactiveCount,
|
||||
'expiring': expiringLicenses.length,
|
||||
'total': activeCount + inactiveCount,
|
||||
};
|
||||
} catch (e) {
|
||||
return {'active': 0, 'inactive': 0, 'expiring': 0, 'total': 0};
|
||||
}
|
||||
} else {
|
||||
// Mock 데이터에서 계산
|
||||
final allLicenses = mockDataService?.getAllLicenses() ?? [];
|
||||
final now = DateTime.now();
|
||||
|
||||
int activeCount = 0;
|
||||
int expiredCount = 0;
|
||||
int expiringCount = 0;
|
||||
|
||||
for (var license in allLicenses) {
|
||||
if (license.isActive) {
|
||||
activeCount++;
|
||||
|
||||
if (license.expiryDate != null) {
|
||||
final daysUntilExpiry = license.expiryDate!.difference(now).inDays;
|
||||
if (daysUntilExpiry <= 0) {
|
||||
expiredCount++;
|
||||
} else if (daysUntilExpiry <= 30) {
|
||||
expiringCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
'active': activeCount,
|
||||
'inactive': allLicenses.length - activeCount,
|
||||
'expiring': expiringCount,
|
||||
'expired': expiredCount,
|
||||
'total': allLicenses.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// 정렬 변경
|
||||
void sortBy(String field, String order) {
|
||||
_sortBy = field;
|
||||
_sortOrder = order;
|
||||
loadData();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_debounceTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,9 @@ class _MaintenanceFormScreenState extends State<MaintenanceFormScreen> {
|
||||
dataService: MockDataService(),
|
||||
licenseId: widget.maintenanceId,
|
||||
);
|
||||
_controller.isEditMode = widget.maintenanceId != null;
|
||||
if (widget.maintenanceId != null) {
|
||||
_controller.isEditMode = true;
|
||||
}
|
||||
if (_controller.isEditMode) {
|
||||
_controller.loadLicense();
|
||||
// TODO: 기존 데이터 로딩 시 _selectedVisitCycle, _selectedInspectionType, _durationMonths 값 세팅 필요
|
||||
@@ -201,10 +203,10 @@ class _MaintenanceFormScreenState extends State<MaintenanceFormScreen> {
|
||||
_controller.durationMonths = _durationMonths;
|
||||
_controller.visitCycle = _selectedVisitCycle;
|
||||
// 점검형태 저장 로직 필요 시 추가
|
||||
setState(() {
|
||||
_controller.saveLicense(() {
|
||||
_controller.saveLicense().then((success) {
|
||||
if (success) {
|
||||
Navigator.pop(context, true);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -23,7 +23,7 @@ class _LicenseListRedesignState extends State<LicenseListRedesign> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = LicenseListController(dataService: _dataService);
|
||||
_controller = LicenseListController(mockDataService: _dataService);
|
||||
_controller.loadData();
|
||||
}
|
||||
|
||||
@@ -294,7 +294,7 @@ class _LicenseListRedesignState extends State<LicenseListRedesign> {
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
_getCompanyName(license.companyId),
|
||||
_getCompanyName(license.companyId ?? 0),
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -35,6 +35,10 @@ class OverviewController extends ChangeNotifier {
|
||||
EquipmentStatusDistribution? get equipmentStatus => _equipmentStatus;
|
||||
List<ExpiringLicense> get expiringLicenses => _expiringLicenses;
|
||||
|
||||
// 추가 getter
|
||||
int get totalCompanies => _overviewStats?.totalCompanies ?? 0;
|
||||
int get totalUsers => _overviewStats?.totalUsers ?? 0;
|
||||
|
||||
bool get isLoading => _isLoadingStats || _isLoadingActivities ||
|
||||
_isLoadingEquipmentStatus || _isLoadingLicenses;
|
||||
|
||||
@@ -55,6 +59,11 @@ class OverviewController extends ChangeNotifier {
|
||||
]);
|
||||
}
|
||||
|
||||
// 대시보드 데이터 로드 (loadData의 alias)
|
||||
Future<void> loadDashboardData() async {
|
||||
await loadData();
|
||||
}
|
||||
|
||||
// 개별 데이터 로드 메서드
|
||||
Future<void> _loadOverviewStats() async {
|
||||
_isLoadingStats = true;
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:superport/screens/common/theme_shadcn.dart';
|
||||
import 'package:superport/screens/common/components/shadcn_components.dart';
|
||||
import 'package:superport/screens/overview/controllers/overview_controller.dart';
|
||||
@@ -123,13 +125,13 @@ class _OverviewScreenRedesignState extends State<OverviewScreenRedesign> {
|
||||
),
|
||||
_buildStatCard(
|
||||
'입고 장비',
|
||||
'${_controller.totalEquipmentIn}',
|
||||
'${_controller.overviewStats?.availableEquipment ?? 0}',
|
||||
Icons.inventory,
|
||||
ShadcnTheme.success,
|
||||
),
|
||||
_buildStatCard(
|
||||
'출고 장비',
|
||||
'${_controller.totalEquipmentOut}',
|
||||
'${_controller.overviewStats?.inUseEquipment ?? 0}',
|
||||
Icons.local_shipping,
|
||||
ShadcnTheme.warning,
|
||||
),
|
||||
@@ -390,7 +392,7 @@ class _OverviewScreenRedesignState extends State<OverviewScreenRedesign> {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
color: color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Icon(icon, color: color, size: 20),
|
||||
@@ -398,7 +400,7 @@ class _OverviewScreenRedesignState extends State<OverviewScreenRedesign> {
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.success.withOpacity(0.1),
|
||||
color: ShadcnTheme.success.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
@@ -474,7 +476,7 @@ class _OverviewScreenRedesignState extends State<OverviewScreenRedesign> {
|
||||
Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
decoration: BoxDecoration(
|
||||
color: color.withOpacity(0.1),
|
||||
color: color.withValues(alpha: 0.1),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: Icon(
|
||||
|
||||
@@ -5,10 +5,6 @@ import 'package:superport/screens/sidebar/widgets/sidebar_menu_footer.dart';
|
||||
import 'package:superport/screens/sidebar/widgets/sidebar_menu_item.dart';
|
||||
import 'package:superport/screens/sidebar/widgets/sidebar_menu_submenu.dart';
|
||||
import 'package:superport/screens/sidebar/widgets/sidebar_menu_types.dart';
|
||||
import 'package:superport/screens/common/theme_tailwind.dart';
|
||||
import 'package:superport/screens/login/widgets/login_view.dart'; // AnimatedBoatIcon import
|
||||
import 'package:wave/wave.dart';
|
||||
import 'package:wave/config.dart';
|
||||
|
||||
// 사이드바 메뉴 메인 위젯 (조립만 담당)
|
||||
class SidebarMenu extends StatefulWidget {
|
||||
|
||||
@@ -4,8 +4,6 @@ import 'package:superport/models/user_model.dart';
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/services/mock_data_service.dart';
|
||||
import 'package:superport/services/user_service.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
import 'package:superport/utils/user_utils.dart';
|
||||
|
||||
/// 담당자 목록 화면의 상태 및 비즈니스 로직을 담당하는 컨트롤러
|
||||
class UserListController extends ChangeNotifier {
|
||||
|
||||
@@ -1,15 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/models/user_model.dart';
|
||||
import 'package:superport/screens/common/theme_tailwind.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/utils/validators.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:superport/screens/user/controllers/user_form_controller.dart';
|
||||
import 'package:superport/models/user_phone_field.dart';
|
||||
import 'package:superport/screens/common/widgets/company_branch_dropdown.dart';
|
||||
|
||||
// 사용자 등록/수정 화면 (UI만 담당, 상태/로직 분리)
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport/models/company_model.dart';
|
||||
import 'package:superport/models/user_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';
|
||||
@@ -82,6 +80,11 @@ class _UserListScreenState extends State<UserListScreen> {
|
||||
onPressed: () {
|
||||
_controller.deleteUser(id, () {
|
||||
Navigator.pop(context);
|
||||
}, (error) {
|
||||
Navigator.pop(context);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(error)),
|
||||
);
|
||||
});
|
||||
},
|
||||
child: const Text('삭제'),
|
||||
|
||||
@@ -211,7 +211,7 @@ class _UserListRedesignState extends State<UserListRedesign> {
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
'데이터를 불러올 수 없습니다',
|
||||
style: ShadcnTheme.h4,
|
||||
style: ShadcnTheme.headingH4,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
@@ -291,7 +291,7 @@ class _UserListRedesignState extends State<UserListRedesign> {
|
||||
: null,
|
||||
);
|
||||
},
|
||||
variant: ShadcnButtonVariant.outline,
|
||||
variant: ShadcnButtonVariant.secondary,
|
||||
icon: const Icon(Icons.filter_list),
|
||||
),
|
||||
const SizedBox(width: ShadcnTheme.spacing2),
|
||||
@@ -302,7 +302,7 @@ class _UserListRedesignState extends State<UserListRedesign> {
|
||||
? '모든 권한'
|
||||
: getRoleName(controller.filterRole!),
|
||||
onPressed: null,
|
||||
variant: ShadcnButtonVariant.outline,
|
||||
variant: ShadcnButtonVariant.secondary,
|
||||
icon: const Icon(Icons.person),
|
||||
),
|
||||
onSelected: (role) {
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:superport/models/address_model.dart';
|
||||
import 'package:superport/screens/common/widgets/address_input.dart';
|
||||
import 'package:superport/screens/common/widgets/remark_input.dart';
|
||||
import 'package:superport/screens/common/theme_tailwind.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
import 'controllers/warehouse_location_form_controller.dart';
|
||||
|
||||
/// 입고지 추가/수정 폼 화면 (SRP 적용, 상태/로직 분리)
|
||||
@@ -26,7 +25,9 @@ class _WarehouseLocationFormScreenState
|
||||
super.initState();
|
||||
// 컨트롤러 생성 및 초기화
|
||||
_controller = WarehouseLocationFormController();
|
||||
_controller.initialize(widget.id);
|
||||
if (widget.id != null) {
|
||||
_controller.initialize(widget.id!);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -107,7 +108,7 @@ class _WarehouseLocationFormScreenState
|
||||
? null
|
||||
: () async {
|
||||
setState(() {}); // 저장 중 상태 갱신
|
||||
await _controller.save(context);
|
||||
await _controller.save();
|
||||
setState(() {}); // 저장 완료 후 상태 갱신
|
||||
},
|
||||
style: ElevatedButton.styleFrom(
|
||||
|
||||
@@ -136,7 +136,7 @@ class _WarehouseLocationListRedesignState
|
||||
vertical: ShadcnTheme.spacing3,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: ShadcnTheme.muted.withOpacity(0.3),
|
||||
color: ShadcnTheme.muted.withValues(alpha: 0.3),
|
||||
border: Border(
|
||||
bottom: BorderSide(color: ShadcnTheme.border),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user