feat: 라이선스 관리 기능 개선 및 폼 검증 강화
- LicenseDto 모델 업데이트 - 라이선스 폼 UI 개선 및 검증 로직 강화 - 라이선스 리스트 화면 필터링 기능 추가 - 만료일 관리 기능 개선 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -25,6 +25,27 @@ DateTime? _dateFromJson(String? dateStr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 문자열이나 숫자를 double로 변환하는 헬퍼 함수
|
||||||
|
double? _priceFromJson(dynamic value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
if (value is double) return value;
|
||||||
|
if (value is int) return value.toDouble();
|
||||||
|
if (value is String) {
|
||||||
|
try {
|
||||||
|
return double.parse(value);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// double을 문자열로 변환하는 헬퍼 함수
|
||||||
|
String? _priceToJson(double? value) {
|
||||||
|
if (value == null) return null;
|
||||||
|
return value.toStringAsFixed(2);
|
||||||
|
}
|
||||||
|
|
||||||
// 필수 날짜 필드용 헬퍼 함수 (항상 non-null DateTime 반환)
|
// 필수 날짜 필드용 헬퍼 함수 (항상 non-null DateTime 반환)
|
||||||
DateTime _requiredDateFromJson(String? dateStr) {
|
DateTime _requiredDateFromJson(String? dateStr) {
|
||||||
if (dateStr == null || dateStr.isEmpty) return DateTime.now();
|
if (dateStr == null || dateStr.isEmpty) return DateTime.now();
|
||||||
@@ -53,7 +74,7 @@ class LicenseDto with _$LicenseDto {
|
|||||||
@JsonKey(name: 'user_count') int? userCount,
|
@JsonKey(name: 'user_count') int? userCount,
|
||||||
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson) DateTime? purchaseDate,
|
@JsonKey(name: 'purchase_date', toJson: _dateToJson, fromJson: _dateFromJson) DateTime? purchaseDate,
|
||||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson) DateTime? expiryDate,
|
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson) DateTime? expiryDate,
|
||||||
@JsonKey(name: 'purchase_price') double? purchasePrice,
|
@JsonKey(name: 'purchase_price', toJson: _priceToJson, fromJson: _priceFromJson) double? purchasePrice,
|
||||||
@JsonKey(name: 'company_id') int? companyId,
|
@JsonKey(name: 'company_id') int? companyId,
|
||||||
@JsonKey(name: 'branch_id') int? branchId,
|
@JsonKey(name: 'branch_id') int? branchId,
|
||||||
@JsonKey(name: 'assigned_user_id') int? assignedUserId,
|
@JsonKey(name: 'assigned_user_id') int? assignedUserId,
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ mixin _$LicenseDto {
|
|||||||
DateTime? get purchaseDate => throw _privateConstructorUsedError;
|
DateTime? get purchaseDate => throw _privateConstructorUsedError;
|
||||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||||
DateTime? get expiryDate => throw _privateConstructorUsedError;
|
DateTime? get expiryDate => throw _privateConstructorUsedError;
|
||||||
@JsonKey(name: 'purchase_price')
|
@JsonKey(
|
||||||
|
name: 'purchase_price', toJson: _priceToJson, fromJson: _priceFromJson)
|
||||||
double? get purchasePrice => throw _privateConstructorUsedError;
|
double? get purchasePrice => throw _privateConstructorUsedError;
|
||||||
@JsonKey(name: 'company_id')
|
@JsonKey(name: 'company_id')
|
||||||
int? get companyId => throw _privateConstructorUsedError;
|
int? get companyId => throw _privateConstructorUsedError;
|
||||||
@@ -86,7 +87,11 @@ abstract class $LicenseDtoCopyWith<$Res> {
|
|||||||
@JsonKey(
|
@JsonKey(
|
||||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||||
DateTime? expiryDate,
|
DateTime? expiryDate,
|
||||||
@JsonKey(name: 'purchase_price') double? purchasePrice,
|
@JsonKey(
|
||||||
|
name: 'purchase_price',
|
||||||
|
toJson: _priceToJson,
|
||||||
|
fromJson: _priceFromJson)
|
||||||
|
double? purchasePrice,
|
||||||
@JsonKey(name: 'company_id') int? companyId,
|
@JsonKey(name: 'company_id') int? companyId,
|
||||||
@JsonKey(name: 'branch_id') int? branchId,
|
@JsonKey(name: 'branch_id') int? branchId,
|
||||||
@JsonKey(name: 'assigned_user_id') int? assignedUserId,
|
@JsonKey(name: 'assigned_user_id') int? assignedUserId,
|
||||||
@@ -236,7 +241,11 @@ abstract class _$$LicenseDtoImplCopyWith<$Res>
|
|||||||
@JsonKey(
|
@JsonKey(
|
||||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||||
DateTime? expiryDate,
|
DateTime? expiryDate,
|
||||||
@JsonKey(name: 'purchase_price') double? purchasePrice,
|
@JsonKey(
|
||||||
|
name: 'purchase_price',
|
||||||
|
toJson: _priceToJson,
|
||||||
|
fromJson: _priceFromJson)
|
||||||
|
double? purchasePrice,
|
||||||
@JsonKey(name: 'company_id') int? companyId,
|
@JsonKey(name: 'company_id') int? companyId,
|
||||||
@JsonKey(name: 'branch_id') int? branchId,
|
@JsonKey(name: 'branch_id') int? branchId,
|
||||||
@JsonKey(name: 'assigned_user_id') int? assignedUserId,
|
@JsonKey(name: 'assigned_user_id') int? assignedUserId,
|
||||||
@@ -379,7 +388,11 @@ class _$LicenseDtoImpl implements _LicenseDto {
|
|||||||
@JsonKey(
|
@JsonKey(
|
||||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||||
this.expiryDate,
|
this.expiryDate,
|
||||||
@JsonKey(name: 'purchase_price') this.purchasePrice,
|
@JsonKey(
|
||||||
|
name: 'purchase_price',
|
||||||
|
toJson: _priceToJson,
|
||||||
|
fromJson: _priceFromJson)
|
||||||
|
this.purchasePrice,
|
||||||
@JsonKey(name: 'company_id') this.companyId,
|
@JsonKey(name: 'company_id') this.companyId,
|
||||||
@JsonKey(name: 'branch_id') this.branchId,
|
@JsonKey(name: 'branch_id') this.branchId,
|
||||||
@JsonKey(name: 'assigned_user_id') this.assignedUserId,
|
@JsonKey(name: 'assigned_user_id') this.assignedUserId,
|
||||||
@@ -417,7 +430,8 @@ class _$LicenseDtoImpl implements _LicenseDto {
|
|||||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||||
final DateTime? expiryDate;
|
final DateTime? expiryDate;
|
||||||
@override
|
@override
|
||||||
@JsonKey(name: 'purchase_price')
|
@JsonKey(
|
||||||
|
name: 'purchase_price', toJson: _priceToJson, fromJson: _priceFromJson)
|
||||||
final double? purchasePrice;
|
final double? purchasePrice;
|
||||||
@override
|
@override
|
||||||
@JsonKey(name: 'company_id')
|
@JsonKey(name: 'company_id')
|
||||||
@@ -552,7 +566,11 @@ abstract class _LicenseDto implements LicenseDto {
|
|||||||
@JsonKey(
|
@JsonKey(
|
||||||
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||||
final DateTime? expiryDate,
|
final DateTime? expiryDate,
|
||||||
@JsonKey(name: 'purchase_price') final double? purchasePrice,
|
@JsonKey(
|
||||||
|
name: 'purchase_price',
|
||||||
|
toJson: _priceToJson,
|
||||||
|
fromJson: _priceFromJson)
|
||||||
|
final double? purchasePrice,
|
||||||
@JsonKey(name: 'company_id') final int? companyId,
|
@JsonKey(name: 'company_id') final int? companyId,
|
||||||
@JsonKey(name: 'branch_id') final int? branchId,
|
@JsonKey(name: 'branch_id') final int? branchId,
|
||||||
@JsonKey(name: 'assigned_user_id') final int? assignedUserId,
|
@JsonKey(name: 'assigned_user_id') final int? assignedUserId,
|
||||||
@@ -591,7 +609,8 @@ abstract class _LicenseDto implements LicenseDto {
|
|||||||
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
@JsonKey(name: 'expiry_date', toJson: _dateToJson, fromJson: _dateFromJson)
|
||||||
DateTime? get expiryDate;
|
DateTime? get expiryDate;
|
||||||
@override
|
@override
|
||||||
@JsonKey(name: 'purchase_price')
|
@JsonKey(
|
||||||
|
name: 'purchase_price', toJson: _priceToJson, fromJson: _priceFromJson)
|
||||||
double? get purchasePrice;
|
double? get purchasePrice;
|
||||||
@override
|
@override
|
||||||
@JsonKey(name: 'company_id')
|
@JsonKey(name: 'company_id')
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ _$LicenseDtoImpl _$$LicenseDtoImplFromJson(Map<String, dynamic> json) =>
|
|||||||
userCount: (json['user_count'] as num?)?.toInt(),
|
userCount: (json['user_count'] as num?)?.toInt(),
|
||||||
purchaseDate: _dateFromJson(json['purchase_date'] as String?),
|
purchaseDate: _dateFromJson(json['purchase_date'] as String?),
|
||||||
expiryDate: _dateFromJson(json['expiry_date'] as String?),
|
expiryDate: _dateFromJson(json['expiry_date'] as String?),
|
||||||
purchasePrice: (json['purchase_price'] as num?)?.toDouble(),
|
purchasePrice: _priceFromJson(json['purchase_price']),
|
||||||
companyId: (json['company_id'] as num?)?.toInt(),
|
companyId: (json['company_id'] as num?)?.toInt(),
|
||||||
branchId: (json['branch_id'] as num?)?.toInt(),
|
branchId: (json['branch_id'] as num?)?.toInt(),
|
||||||
assignedUserId: (json['assigned_user_id'] as num?)?.toInt(),
|
assignedUserId: (json['assigned_user_id'] as num?)?.toInt(),
|
||||||
@@ -39,7 +39,7 @@ Map<String, dynamic> _$$LicenseDtoImplToJson(_$LicenseDtoImpl instance) =>
|
|||||||
'user_count': instance.userCount,
|
'user_count': instance.userCount,
|
||||||
'purchase_date': _dateToJson(instance.purchaseDate),
|
'purchase_date': _dateToJson(instance.purchaseDate),
|
||||||
'expiry_date': _dateToJson(instance.expiryDate),
|
'expiry_date': _dateToJson(instance.expiryDate),
|
||||||
'purchase_price': instance.purchasePrice,
|
'purchase_price': _priceToJson(instance.purchasePrice),
|
||||||
'company_id': instance.companyId,
|
'company_id': instance.companyId,
|
||||||
'branch_id': instance.branchId,
|
'branch_id': instance.branchId,
|
||||||
'assigned_user_id': instance.assignedUserId,
|
'assigned_user_id': instance.assignedUserId,
|
||||||
|
|||||||
@@ -23,6 +23,16 @@ class LicenseFormController extends ChangeNotifier {
|
|||||||
int _companyId = 1;
|
int _companyId = 1;
|
||||||
int _durationMonths = 12; // 기본값: 12개월
|
int _durationMonths = 12; // 기본값: 12개월
|
||||||
String _visitCycle = '미방문'; // 기본값: 미방문
|
String _visitCycle = '미방문'; // 기본값: 미방문
|
||||||
|
|
||||||
|
// 추가 필드 컨트롤러
|
||||||
|
final TextEditingController productNameController = TextEditingController();
|
||||||
|
final TextEditingController licenseKeyController = TextEditingController();
|
||||||
|
final TextEditingController vendorController = TextEditingController();
|
||||||
|
final TextEditingController locationController = TextEditingController();
|
||||||
|
final TextEditingController assignedUserController = TextEditingController();
|
||||||
|
String status = '활성';
|
||||||
|
DateTime? purchaseDate;
|
||||||
|
DateTime? expiryDate;
|
||||||
|
|
||||||
// isEditMode setter
|
// isEditMode setter
|
||||||
set isEditMode(bool value) {
|
set isEditMode(bool value) {
|
||||||
@@ -52,15 +62,19 @@ class LicenseFormController extends ChangeNotifier {
|
|||||||
this.useApi = false,
|
this.useApi = false,
|
||||||
MockDataService? dataService,
|
MockDataService? dataService,
|
||||||
int? licenseId,
|
int? licenseId,
|
||||||
|
bool isExtension = false,
|
||||||
}) : mockDataService = dataService ?? MockDataService() {
|
}) : mockDataService = dataService ?? MockDataService() {
|
||||||
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
||||||
_licenseService = GetIt.instance<LicenseService>();
|
_licenseService = GetIt.instance<LicenseService>();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (licenseId != null) {
|
if (licenseId != null && !isExtension) {
|
||||||
_licenseId = licenseId;
|
_licenseId = licenseId;
|
||||||
_isEditMode = true;
|
_isEditMode = true;
|
||||||
loadLicense();
|
// loadLicense()는 별도로 호출됨
|
||||||
|
} else if (licenseId != null && isExtension) {
|
||||||
|
_licenseId = licenseId;
|
||||||
|
_isEditMode = false; // 연장 모드는 새로운 라이선스 생성
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,29 +115,122 @@ class LicenseFormController extends ChangeNotifier {
|
|||||||
Future<void> loadLicense() async {
|
Future<void> loadLicense() async {
|
||||||
if (_licenseId == null) return;
|
if (_licenseId == null) return;
|
||||||
|
|
||||||
|
debugPrint('📝 loadLicense 시작 - ID: $_licenseId');
|
||||||
|
|
||||||
_isLoading = true;
|
_isLoading = true;
|
||||||
_error = null;
|
_error = null;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
||||||
|
debugPrint('📝 API에서 라이센스 로드 중...');
|
||||||
_originalLicense = await _licenseService.getLicenseById(_licenseId!);
|
_originalLicense = await _licenseService.getLicenseById(_licenseId!);
|
||||||
} else {
|
} else {
|
||||||
|
debugPrint('📝 Mock에서 라이센스 로드 중...');
|
||||||
_originalLicense = mockDataService?.getLicenseById(_licenseId!);
|
_originalLicense = mockDataService?.getLicenseById(_licenseId!);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
debugPrint('📝 로드된 라이센스: $_originalLicense');
|
||||||
|
|
||||||
if (_originalLicense != null) {
|
if (_originalLicense != null) {
|
||||||
|
// 폼 필드에 데이터 설정
|
||||||
|
productNameController.text = _originalLicense!.productName ?? '';
|
||||||
|
licenseKeyController.text = _originalLicense!.licenseKey;
|
||||||
|
vendorController.text = _originalLicense!.vendor ?? '';
|
||||||
|
locationController.text = _originalLicense!.companyName ?? '';
|
||||||
|
assignedUserController.text = _originalLicense!.assignedUserName ?? '';
|
||||||
|
|
||||||
|
debugPrint('📝 폼 필드 설정 완료:');
|
||||||
|
debugPrint(' - 제품명: ${productNameController.text}');
|
||||||
|
debugPrint(' - 라이선스 키: ${licenseKeyController.text}');
|
||||||
|
debugPrint(' - 벤더: ${vendorController.text}');
|
||||||
|
debugPrint(' - 현위치: ${locationController.text}');
|
||||||
|
debugPrint(' - 할당 사용자: ${assignedUserController.text}');
|
||||||
|
status = _originalLicense!.isActive ? '활성' : '비활성';
|
||||||
|
purchaseDate = _originalLicense!.purchaseDate;
|
||||||
|
expiryDate = _originalLicense!.expiryDate;
|
||||||
|
|
||||||
_name = _originalLicense!.productName ?? '';
|
_name = _originalLicense!.productName ?? '';
|
||||||
_companyId = _originalLicense!.companyId ?? 1;
|
_companyId = _originalLicense!.companyId ?? 1;
|
||||||
// durationMonths와 visitCycle은 License 모델에 없으므로 기본값 유지
|
|
||||||
// _durationMonths = _originalLicense!.durationMonths;
|
// remark에서 방문주기 정보 추출 (있는 경우)
|
||||||
// _visitCycle = _originalLicense!.visitCycle;
|
if (_originalLicense!.remark != null && _originalLicense!.remark!.contains('방문주기:')) {
|
||||||
|
final remarkParts = _originalLicense!.remark!.split('방문주기:');
|
||||||
|
if (remarkParts.length > 1) {
|
||||||
|
_visitCycle = remarkParts[1].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
_error = e.toString();
|
_error = e.toString();
|
||||||
|
debugPrint('❌ 라이센스 로드 실패: $e');
|
||||||
} finally {
|
} finally {
|
||||||
_isLoading = false;
|
_isLoading = false;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
debugPrint('📝 loadLicense 완료 - isLoading: false');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 라이센스 정보 로드 (연장 모드 - 기존 데이터로 새 라이선스 생성)
|
||||||
|
Future<void> loadLicenseForExtension() async {
|
||||||
|
if (_licenseId == null) return;
|
||||||
|
|
||||||
|
debugPrint('📝 loadLicenseForExtension 시작 - ID: $_licenseId');
|
||||||
|
|
||||||
|
_isLoading = true;
|
||||||
|
_error = null;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
try {
|
||||||
|
License? sourceLicense;
|
||||||
|
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
||||||
|
debugPrint('📝 API에서 라이센스 로드 중 (연장용)...');
|
||||||
|
sourceLicense = await _licenseService.getLicenseById(_licenseId!);
|
||||||
|
} else {
|
||||||
|
debugPrint('📝 Mock에서 라이센스 로드 중 (연장용)...');
|
||||||
|
sourceLicense = mockDataService?.getLicenseById(_licenseId!);
|
||||||
|
}
|
||||||
|
|
||||||
|
debugPrint('📝 로드된 소스 라이센스: $sourceLicense');
|
||||||
|
|
||||||
|
if (sourceLicense != null) {
|
||||||
|
// 연장용으로 기존 데이터 복사 (ID는 null로 새 라이선스 생성)
|
||||||
|
productNameController.text = sourceLicense.productName ?? '';
|
||||||
|
licenseKeyController.text = '${sourceLicense.licenseKey}-EXT-${DateTime.now().millisecondsSinceEpoch}';
|
||||||
|
vendorController.text = sourceLicense.vendor ?? '';
|
||||||
|
locationController.text = sourceLicense.companyName ?? '';
|
||||||
|
assignedUserController.text = sourceLicense.assignedUserName ?? '';
|
||||||
|
|
||||||
|
debugPrint('📝 연장용 폼 필드 설정 완료:');
|
||||||
|
debugPrint(' - 제품명: ${productNameController.text}');
|
||||||
|
debugPrint(' - 라이선스 키: ${licenseKeyController.text}');
|
||||||
|
debugPrint(' - 벤더: ${vendorController.text}');
|
||||||
|
debugPrint(' - 현위치: ${locationController.text}');
|
||||||
|
debugPrint(' - 할당 사용자: ${assignedUserController.text}');
|
||||||
|
status = '활성'; // 연장은 항상 활성으로 시작
|
||||||
|
purchaseDate = DateTime.now(); // 구매일은 오늘
|
||||||
|
// 만료일은 기존 만료일에서 연장 (기본 12개월)
|
||||||
|
expiryDate = sourceLicense.expiryDate?.add(Duration(days: _durationMonths * 30))
|
||||||
|
?? DateTime.now().add(Duration(days: _durationMonths * 30));
|
||||||
|
|
||||||
|
_name = sourceLicense.productName ?? '';
|
||||||
|
_companyId = sourceLicense.companyId ?? 1;
|
||||||
|
|
||||||
|
// remark에서 방문주기 정보 추출 (있는 경우)
|
||||||
|
if (sourceLicense.remark != null && sourceLicense.remark!.contains('방문주기:')) {
|
||||||
|
final remarkParts = sourceLicense.remark!.split('방문주기:');
|
||||||
|
if (remarkParts.length > 1) {
|
||||||
|
_visitCycle = remarkParts[1].trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
_error = e.toString();
|
||||||
|
debugPrint('❌ 라이센스 연장 로드 실패: $e');
|
||||||
|
} finally {
|
||||||
|
_isLoading = false;
|
||||||
|
notifyListeners();
|
||||||
|
debugPrint('📝 loadLicenseForExtension 완료 - isLoading: false');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,14 +247,20 @@ class LicenseFormController extends ChangeNotifier {
|
|||||||
try {
|
try {
|
||||||
final license = License(
|
final license = License(
|
||||||
id: _isEditMode ? _licenseId : null,
|
id: _isEditMode ? _licenseId : null,
|
||||||
licenseKey: 'LIC-${DateTime.now().millisecondsSinceEpoch}',
|
licenseKey: licenseKeyController.text.isNotEmpty
|
||||||
productName: _name,
|
? licenseKeyController.text
|
||||||
|
: 'LIC-${DateTime.now().millisecondsSinceEpoch}',
|
||||||
|
productName: productNameController.text,
|
||||||
|
vendor: vendorController.text,
|
||||||
|
companyName: locationController.text,
|
||||||
|
assignedUserName: assignedUserController.text.isNotEmpty
|
||||||
|
? assignedUserController.text
|
||||||
|
: null,
|
||||||
companyId: _companyId,
|
companyId: _companyId,
|
||||||
// durationMonths와 visitCycle은 License 모델에 없음
|
isActive: status == '활성',
|
||||||
// 대신 expiryDate를 설정
|
purchaseDate: purchaseDate ?? DateTime.now(),
|
||||||
purchaseDate: DateTime.now(),
|
expiryDate: expiryDate ?? DateTime.now().add(Duration(days: _durationMonths * 30)),
|
||||||
expiryDate: DateTime.now().add(Duration(days: _durationMonths * 30)),
|
remark: '${_durationMonths}개월,${_visitCycle},방문',
|
||||||
remark: '방문주기: $_visitCycle',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
if (useApi && GetIt.instance.isRegistered<LicenseService>()) {
|
||||||
@@ -210,6 +323,12 @@ class LicenseFormController extends ChangeNotifier {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
// TextEditingController들 정리
|
||||||
|
productNameController.dispose();
|
||||||
|
licenseKeyController.dispose();
|
||||||
|
vendorController.dispose();
|
||||||
|
locationController.dispose();
|
||||||
|
assignedUserController.dispose();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,14 +3,22 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:superport/models/license_model.dart';
|
import 'package:superport/models/license_model.dart';
|
||||||
import 'package:superport/screens/license/controllers/license_form_controller.dart';
|
import 'package:superport/screens/license/controllers/license_form_controller.dart';
|
||||||
import 'package:superport/screens/common/theme_tailwind.dart';
|
import 'package:superport/screens/common/theme_tailwind.dart';
|
||||||
import 'package:superport/screens/common/custom_widgets.dart';
|
import 'package:superport/screens/common/templates/form_layout_template.dart';
|
||||||
|
import 'package:superport/screens/common/custom_widgets.dart' hide FormFieldWrapper;
|
||||||
import 'package:superport/services/mock_data_service.dart';
|
import 'package:superport/services/mock_data_service.dart';
|
||||||
import 'package:superport/utils/validators.dart';
|
import 'package:superport/utils/validators.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:superport/core/config/environment.dart' as env;
|
||||||
|
|
||||||
// 유지보수 등록/수정 화면 (UI만 담당, 상태/로직 분리)
|
// 유지보수 등록/수정 화면 (UI만 담당, 상태/로직 분리)
|
||||||
class MaintenanceFormScreen extends StatefulWidget {
|
class MaintenanceFormScreen extends StatefulWidget {
|
||||||
final int? maintenanceId;
|
final int? maintenanceId;
|
||||||
const MaintenanceFormScreen({Key? key, this.maintenanceId}) : super(key: key);
|
final bool isExtension; // 연장 모드 여부
|
||||||
|
const MaintenanceFormScreen({
|
||||||
|
Key? key,
|
||||||
|
this.maintenanceId,
|
||||||
|
this.isExtension = false,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_MaintenanceFormScreenState createState() => _MaintenanceFormScreenState();
|
_MaintenanceFormScreenState createState() => _MaintenanceFormScreenState();
|
||||||
@@ -37,16 +45,78 @@ class _MaintenanceFormScreenState extends State<MaintenanceFormScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
// API 모드 확인
|
||||||
|
final useApi = env.Environment.useApi;
|
||||||
|
debugPrint('📌 라이선스 폼 초기화 - API 모드: $useApi');
|
||||||
|
|
||||||
_controller = LicenseFormController(
|
_controller = LicenseFormController(
|
||||||
dataService: MockDataService(),
|
useApi: useApi,
|
||||||
|
dataService: useApi ? null : MockDataService(),
|
||||||
licenseId: widget.maintenanceId,
|
licenseId: widget.maintenanceId,
|
||||||
|
isExtension: widget.isExtension,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 컨트롤러 변경 리스너 등록 (데이터 로드 전에 등록!)
|
||||||
|
_controller.addListener(_handleControllerUpdate);
|
||||||
|
|
||||||
|
// 수정 모드 또는 연장 모드일 때
|
||||||
if (widget.maintenanceId != null) {
|
if (widget.maintenanceId != null) {
|
||||||
_controller.isEditMode = true;
|
// 초기 데이터 로드
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
if (widget.isExtension) {
|
||||||
|
// 연장 모드: 기존 데이터를 로드하되 새로운 라이선스로 생성
|
||||||
|
_controller.isEditMode = false;
|
||||||
|
await _controller.loadLicenseForExtension();
|
||||||
|
} else {
|
||||||
|
// 수정 모드: 기존 라이선스 수정
|
||||||
|
_controller.isEditMode = true;
|
||||||
|
await _controller.loadLicense();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 데이터 로드 후 UI 업데이트
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {
|
||||||
|
// 로드된 데이터로 상태 업데이트
|
||||||
|
_selectedVisitCycle = _controller.visitCycle;
|
||||||
|
_durationMonths = _controller.durationMonths;
|
||||||
|
// 폼 필드들은 컨트롤러의 TextEditingController를 통해 자동 업데이트됨
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (_controller.isEditMode) {
|
}
|
||||||
_controller.loadLicense();
|
|
||||||
// TODO: 기존 데이터 로딩 시 _selectedVisitCycle, _selectedInspectionType, _durationMonths 값 세팅 필요
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_controller.removeListener(_handleControllerUpdate);
|
||||||
|
_controller.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleControllerUpdate() {
|
||||||
|
if (mounted) {
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 저장 메소드
|
||||||
|
Future<void> _onSave() async {
|
||||||
|
if (_controller.formKey.currentState!.validate()) {
|
||||||
|
_controller.formKey.currentState!.save();
|
||||||
|
await _controller.saveLicense();
|
||||||
|
if (mounted) {
|
||||||
|
String message = widget.isExtension
|
||||||
|
? '유지보수가 연장되었습니다'
|
||||||
|
: (_controller.isEditMode ? '유지보수가 수정되었습니다' : '유지보수가 등록되었습니다');
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(message),
|
||||||
|
backgroundColor: AppThemeTailwind.success,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,48 +125,225 @@ class _MaintenanceFormScreenState extends State<MaintenanceFormScreen> {
|
|||||||
// 유지보수 명은 유지보수기간, 방문주기, 점검형태를 결합해서 표기
|
// 유지보수 명은 유지보수기간, 방문주기, 점검형태를 결합해서 표기
|
||||||
final String maintenanceName =
|
final String maintenanceName =
|
||||||
'${_durationMonths}개월,${_selectedVisitCycle},${_selectedInspectionType}';
|
'${_durationMonths}개월,${_selectedVisitCycle},${_selectedInspectionType}';
|
||||||
return Scaffold(
|
|
||||||
appBar: AppBar(
|
return FormLayoutTemplate(
|
||||||
title: Text(_controller.isEditMode ? '유지보수 수정' : '유지보수 등록'),
|
title: widget.isExtension
|
||||||
),
|
? '유지보수 연장'
|
||||||
body: Padding(
|
: (_controller.isEditMode ? '유지보수 수정' : '유지보수 등록'),
|
||||||
padding: const EdgeInsets.all(16.0),
|
onSave: _onSave,
|
||||||
child: Form(
|
saveButtonText: widget.isExtension
|
||||||
key: _controller.formKey,
|
? '연장 완료'
|
||||||
child: SingleChildScrollView(
|
: (_controller.isEditMode ? '수정 완료' : '등록 완료'),
|
||||||
child: Column(
|
child: _controller.isLoading
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
? const Center(
|
||||||
children: [
|
child: CircularProgressIndicator(),
|
||||||
// 유지보수 명 표기 (입력 불가, 자동 생성)
|
)
|
||||||
Padding(
|
: Form(
|
||||||
padding: const EdgeInsets.only(bottom: 16.0),
|
key: _controller.formKey,
|
||||||
child: Column(
|
child: SingleChildScrollView(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
padding: const EdgeInsets.all(UIConstants.formPadding),
|
||||||
children: [
|
child: Column(
|
||||||
const Text(
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
'유지보수 명',
|
children: [
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
// 기본 정보 섹션
|
||||||
|
FormSection(
|
||||||
|
title: '기본 정보',
|
||||||
|
subtitle: '유지보수의 기본 정보를 입력하세요',
|
||||||
|
children: [
|
||||||
|
// 제품명
|
||||||
|
FormFieldWrapper(
|
||||||
|
label: '제품명',
|
||||||
|
required: true,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _controller.productNameController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: '제품명을 입력하세요',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
validator: (value) => validateRequired(value, '제품명'),
|
||||||
Container(
|
),
|
||||||
width: double.infinity,
|
),
|
||||||
padding: const EdgeInsets.symmetric(
|
// 라이선스 키
|
||||||
vertical: 12,
|
FormFieldWrapper(
|
||||||
horizontal: 8,
|
label: '라이선스 키',
|
||||||
|
required: true,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _controller.licenseKeyController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: '라이선스 키를 입력하세요',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) => validateRequired(value, '라이선스 키'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 벤더
|
||||||
|
FormFieldWrapper(
|
||||||
|
label: '벤더',
|
||||||
|
required: true,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _controller.vendorController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: '벤더명을 입력하세요',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) => validateRequired(value, '벤더'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 현위치
|
||||||
|
FormFieldWrapper(
|
||||||
|
label: '현위치',
|
||||||
|
required: true,
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _controller.locationController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: '현재 위치를 입력하세요',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) => validateRequired(value, '현위치'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 할당 사용자
|
||||||
|
FormFieldWrapper(
|
||||||
|
label: '할당 사용자',
|
||||||
|
child: TextFormField(
|
||||||
|
controller: _controller.assignedUserController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
hintText: '할당된 사용자를 입력하세요',
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 상태
|
||||||
|
FormFieldWrapper(
|
||||||
|
label: '상태',
|
||||||
|
required: true,
|
||||||
|
child: DropdownButtonFormField<String>(
|
||||||
|
value: _controller.status,
|
||||||
|
items: ['활성', '비활성', '만료'].map((status) =>
|
||||||
|
DropdownMenuItem(
|
||||||
|
value: status,
|
||||||
|
child: Text(status),
|
||||||
),
|
),
|
||||||
decoration: BoxDecoration(
|
).toList(),
|
||||||
border: Border.all(color: Colors.grey.shade400),
|
onChanged: (value) => setState(() => _controller.status = value!),
|
||||||
borderRadius: BorderRadius.circular(4),
|
decoration: const InputDecoration(
|
||||||
color: Colors.grey.shade100,
|
border: OutlineInputBorder(),
|
||||||
|
),
|
||||||
|
validator: (value) => validateRequired(value, '상태'),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 구매일
|
||||||
|
FormFieldWrapper(
|
||||||
|
label: '구매일',
|
||||||
|
required: true,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
final date = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _controller.purchaseDate ?? DateTime.now(),
|
||||||
|
firstDate: DateTime(2000),
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
if (date != null) {
|
||||||
|
setState(() => _controller.purchaseDate = date);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: InputDecorator(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today),
|
||||||
),
|
),
|
||||||
child: Text(
|
child: Text(
|
||||||
maintenanceName,
|
_controller.purchaseDate != null
|
||||||
style: const TextStyle(fontSize: 16),
|
? DateFormat('yyyy-MM-dd').format(_controller.purchaseDate!)
|
||||||
|
: '구매일을 선택하세요',
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
|
),
|
||||||
|
// 만료일
|
||||||
|
FormFieldWrapper(
|
||||||
|
label: '만료일',
|
||||||
|
required: true,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
final date = await showDatePicker(
|
||||||
|
context: context,
|
||||||
|
initialDate: _controller.expiryDate ?? DateTime.now(),
|
||||||
|
firstDate: DateTime(2000),
|
||||||
|
lastDate: DateTime(2100),
|
||||||
|
);
|
||||||
|
if (date != null) {
|
||||||
|
setState(() => _controller.expiryDate = date);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: InputDecorator(
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
suffixIcon: Icon(Icons.calendar_today),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
_controller.expiryDate != null
|
||||||
|
? DateFormat('yyyy-MM-dd').format(_controller.expiryDate!)
|
||||||
|
: '만료일을 선택하세요',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// 남은 일수 (자동 계산)
|
||||||
|
if (_controller.expiryDate != null)
|
||||||
|
FormFieldWrapper(
|
||||||
|
label: '남은 일수',
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 12),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: UIConstants.borderColor),
|
||||||
|
borderRadius: BorderRadius.circular(UIConstants.borderRadius),
|
||||||
|
color: UIConstants.backgroundColor,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'${_controller.expiryDate!.difference(DateTime.now()).inDays}일',
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: _controller.expiryDate!.difference(DateTime.now()).inDays < 30
|
||||||
|
? Colors.red
|
||||||
|
: _controller.expiryDate!.difference(DateTime.now()).inDays < 90
|
||||||
|
? Colors.orange
|
||||||
|
: Colors.green,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
// 유지보수 설정 섹션
|
||||||
|
FormSection(
|
||||||
|
title: '유지보수 설정',
|
||||||
|
subtitle: '유지보수 기간 및 방문 주기를 설정하세요',
|
||||||
|
children: [
|
||||||
|
// 유지보수 명 표기 (입력 불가, 자동 생성)
|
||||||
|
FormFieldWrapper(
|
||||||
|
label: '유지보수 명',
|
||||||
|
hint: '유지보수 기간, 방문 주기, 점검 형태를 조합하여 자동 생성됩니다',
|
||||||
|
child: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 12,
|
||||||
|
horizontal: 12,
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: UIConstants.borderColor),
|
||||||
|
borderRadius: BorderRadius.circular(UIConstants.borderRadius),
|
||||||
|
color: UIConstants.backgroundColor,
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
maintenanceName,
|
||||||
|
style: const TextStyle(fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
// 유지보수 기간 (개월)
|
// 유지보수 기간 (개월)
|
||||||
_buildTextField(
|
_buildTextField(
|
||||||
label: '유지보수 기간 (개월)',
|
label: '유지보수 기간 (개월)',
|
||||||
@@ -156,75 +403,34 @@ class _MaintenanceFormScreenState extends State<MaintenanceFormScreen> {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 점검 형태 (라디오버튼)
|
// 점검 형태 (라디오버튼)
|
||||||
Padding(
|
FormFieldWrapper(
|
||||||
padding: const EdgeInsets.only(bottom: 16.0),
|
label: '점검 형태',
|
||||||
child: Column(
|
required: true,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
child: Row(
|
||||||
children: [
|
children: _inspectionTypeOptions.map((type) {
|
||||||
const Text(
|
return Expanded(
|
||||||
'점검 형태',
|
child: RadioListTile<String>(
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
title: Text(type),
|
||||||
),
|
value: type,
|
||||||
const SizedBox(height: 4),
|
groupValue: _selectedInspectionType,
|
||||||
Row(
|
onChanged: (value) {
|
||||||
children:
|
setState(() {
|
||||||
_inspectionTypeOptions.map((type) {
|
_selectedInspectionType = value!;
|
||||||
return Row(
|
});
|
||||||
children: [
|
},
|
||||||
Radio<String>(
|
contentPadding: EdgeInsets.zero,
|
||||||
value: type,
|
dense: true,
|
||||||
groupValue: _selectedInspectionType,
|
),
|
||||||
onChanged: (value) {
|
);
|
||||||
setState(() {
|
}).toList(),
|
||||||
_selectedInspectionType = value!;
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
Text(type),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}).toList(),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 24),
|
], // FormSection children
|
||||||
// 저장 버튼
|
), // FormSection 끝
|
||||||
SizedBox(
|
], // Column children
|
||||||
width: double.infinity,
|
), // SingleChildScrollView child
|
||||||
child: ElevatedButton(
|
), // Form child
|
||||||
onPressed: () {
|
), // FormLayoutTemplate child
|
||||||
if (_controller.formKey.currentState!.validate()) {
|
|
||||||
_controller.formKey.currentState!.save();
|
|
||||||
// 유지보수 명 결합하여 저장
|
|
||||||
final String saveName =
|
|
||||||
'${_durationMonths}개월,${_selectedVisitCycle},${_selectedInspectionType}';
|
|
||||||
_controller.name = saveName;
|
|
||||||
_controller.durationMonths = _durationMonths;
|
|
||||||
_controller.visitCycle = _selectedVisitCycle;
|
|
||||||
// 점검형태 저장 로직 필요 시 추가
|
|
||||||
_controller.saveLicense().then((success) {
|
|
||||||
if (success) {
|
|
||||||
Navigator.pop(context, true);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
style: AppThemeTailwind.primaryButtonStyle,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(12.0),
|
|
||||||
child: Text(
|
|
||||||
_controller.isEditMode ? '수정하기' : '등록하기',
|
|
||||||
style: const TextStyle(fontSize: 16),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -84,9 +84,31 @@ class _LicenseListRedesignState extends State<LicenseListRedesign> {
|
|||||||
_controller.search(_searchController.text);
|
_controller.search(_searchController.text);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 라이선스 추가 폼으로 이동
|
/// 유지보수 연장 폼으로 이동
|
||||||
void _navigateToAdd() async {
|
void _navigateToAdd() async {
|
||||||
final result = await Navigator.pushNamed(context, Routes.licenseAdd);
|
// 선택된 라이선스 확인
|
||||||
|
final selectedLicenses = _controller.getSelectedLicenses();
|
||||||
|
|
||||||
|
// 선택된 라이선스가 1개인 경우 해당 라이선스 ID 전달
|
||||||
|
int? licenseId;
|
||||||
|
if (selectedLicenses.length == 1) {
|
||||||
|
licenseId = selectedLicenses.first.id;
|
||||||
|
} else if (selectedLicenses.length > 1) {
|
||||||
|
// 여러 개 선택된 경우 경고 메시지
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('유지보수 연장은 한 번에 하나의 라이선스만 선택할 수 있습니다.'),
|
||||||
|
duration: Duration(seconds: 2),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = await Navigator.pushNamed(
|
||||||
|
context,
|
||||||
|
Routes.licenseAdd,
|
||||||
|
arguments: licenseId, // 선택된 라이선스 ID 전달 (없으면 null)
|
||||||
|
);
|
||||||
if (result == true && mounted) {
|
if (result == true && mounted) {
|
||||||
_controller.refresh();
|
_controller.refresh();
|
||||||
}
|
}
|
||||||
@@ -352,7 +374,7 @@ class _LicenseListRedesignState extends State<LicenseListRedesign> {
|
|||||||
controller: _searchController,
|
controller: _searchController,
|
||||||
onSubmitted: (_) => _onSearch(),
|
onSubmitted: (_) => _onSearch(),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
hintText: '제품명, 라이선스 키, 벤더명, 회사명 검색...',
|
hintText: '제품명, 라이선스 키, 벤더명, 현위치 검색...',
|
||||||
hintStyle: TextStyle(color: ShadcnTheme.mutedForeground.withValues(alpha: 0.8), fontSize: 14),
|
hintStyle: TextStyle(color: ShadcnTheme.mutedForeground.withValues(alpha: 0.8), fontSize: 14),
|
||||||
prefixIcon: Icon(Icons.search, color: ShadcnTheme.muted, size: 20),
|
prefixIcon: Icon(Icons.search, color: ShadcnTheme.muted, size: 20),
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
@@ -431,7 +453,7 @@ class _LicenseListRedesignState extends State<LicenseListRedesign> {
|
|||||||
children: [
|
children: [
|
||||||
// 액션 버튼들
|
// 액션 버튼들
|
||||||
ShadcnButton(
|
ShadcnButton(
|
||||||
text: '라이선스 추가',
|
text: '유지보수 연장',
|
||||||
onPressed: _navigateToAdd,
|
onPressed: _navigateToAdd,
|
||||||
variant: ShadcnButtonVariant.primary,
|
variant: ShadcnButtonVariant.primary,
|
||||||
textColor: Colors.white,
|
textColor: Colors.white,
|
||||||
@@ -634,10 +656,10 @@ class _LicenseListRedesignState extends State<LicenseListRedesign> {
|
|||||||
width: 120,
|
width: 120,
|
||||||
child: Text('벤더', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)),
|
child: Text('벤더', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)),
|
||||||
),
|
),
|
||||||
// 회사명
|
// 현위치
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 150,
|
width: 150,
|
||||||
child: Text('회사명', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)),
|
child: Text('현위치', style: TextStyle(fontSize: 13, fontWeight: FontWeight.w500)),
|
||||||
),
|
),
|
||||||
// 할당 사용자
|
// 할당 사용자
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
@@ -735,7 +757,7 @@ class _LicenseListRedesignState extends State<LicenseListRedesign> {
|
|||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
// 회사명
|
// 현위치
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 150,
|
width: 150,
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|||||||
Reference in New Issue
Block a user