feat: API 연동 개선 및 라이선스 모델 확장

- 라이선스 모델 전면 개편 (상세 필드 추가, 계산 필드 구현)
- API 응답 처리 개선 (HTTP 상태 코드 기반)
- 장비 출고 폼 컨트롤러 추가
- 회사 지점 정보 모델 추가
- 공통 데이터 모델 구조 추가
- 전체 서비스 레이어 API 호출 방식 통일
- UI 컴포넌트 마이너 개선
This commit is contained in:
JiWoong Sul
2025-07-25 01:22:15 +09:00
parent 8384423cf2
commit 71b7b7f40b
42 changed files with 1543 additions and 315 deletions

View File

@@ -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(

View File

@@ -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(

View File

@@ -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();

View File

@@ -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();
}
}

View File

@@ -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,

View File

@@ -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>()) {

View File

@@ -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();
}
}

View File

@@ -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);
});
}
});
}
},

View File

@@ -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,
),
),

View File

@@ -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;

View File

@@ -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(

View File

@@ -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 {

View File

@@ -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 {

View File

@@ -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만 담당, 상태/로직 분리)

View File

@@ -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('삭제'),

View File

@@ -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) {

View File

@@ -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(

View File

@@ -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),
),