Files
superport/lib/screens/model/model_form_dialog.dart
JiWoong Sul c419f8f458 backup: 사용하지 않는 파일 삭제 전 복구 지점
- 전체 371개 파일 중 82개 미사용 파일 식별
- Phase 1: 33개 파일 삭제 예정 (100% 안전)
- Phase 2: 30개 파일 삭제 검토 예정
- Phase 3: 19개 파일 수동 검토 예정

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
2025-09-02 19:51:40 +09:00

309 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:superport/data/models/model/model_dto.dart';
import 'package:superport/screens/model/controllers/model_controller.dart';
class ModelFormDialog extends StatefulWidget {
final ModelController controller;
final ModelDto? model;
const ModelFormDialog({
super.key,
required this.controller,
this.model,
});
@override
State<ModelFormDialog> createState() => _ModelFormDialogState();
}
class _ModelFormDialogState extends State<ModelFormDialog> {
final _formKey = GlobalKey<FormState>();
late final TextEditingController _nameController;
int? _selectedVendorId;
bool _isSubmitting = false;
String? _statusMessage;
@override
void initState() {
super.initState();
_nameController = TextEditingController(text: widget.model?.name);
_selectedVendorId = widget.model?.vendorsId;
}
@override
void dispose() {
_nameController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final isEditMode = widget.model != null;
return ShadDialog(
title: Text(isEditMode ? '모델 수정' : '새 모델 등록'),
child: Container(
width: 400,
padding: const EdgeInsets.all(16),
child: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// 제조사 선택 (3단계 상태 처리)
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text('제조사 선택 *', style: TextStyle(fontWeight: FontWeight.bold)),
const SizedBox(height: 8),
// 3단계 상태 처리 (UserForm 패턴 적용)
widget.controller.isLoadingVendors
? const SizedBox(
height: 56,
child: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ShadProgress(),
SizedBox(width: 8),
Text('제조사 목록을 불러오는 중...'),
],
),
),
)
// 오류 발생 시 오류 컨테이너 표시
: widget.controller.vendorsError != null
? Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.red.shade50,
border: Border.all(color: Colors.red.shade200),
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(Icons.error, color: Colors.red.shade600, size: 20),
const SizedBox(width: 8),
const Text('제조사 목록 로딩 실패', style: TextStyle(fontWeight: FontWeight.w500)),
],
),
const SizedBox(height: 8),
Text(
widget.controller.vendorsError!,
style: TextStyle(color: Colors.red.shade700, fontSize: 14),
),
const SizedBox(height: 12),
ShadButton(
onPressed: () => widget.controller.loadInitialData(),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.refresh, size: 16),
SizedBox(width: 4),
Text('다시 시도'),
],
),
),
],
),
)
// 정상 상태: 드롭다운 표시
: ShadSelect<int>(
placeholder: const Text('제조사를 선택하세요'),
options: widget.controller.vendors.map(
(vendor) => ShadOption(
value: vendor.id,
child: Text(vendor.name),
),
).toList(),
selectedOptionBuilder: (context, value) {
final vendor = widget.controller.vendors.firstWhere(
(v) => v.id == value,
);
return Text(vendor.name);
},
onChanged: (value) {
setState(() {
_selectedVendorId = value;
});
},
initialValue: _selectedVendorId,
),
],
),
const SizedBox(height: 16),
// 모델명 입력
ShadInputFormField(
controller: _nameController,
label: const Text('모델명'),
placeholder: const Text('모델명을 입력하세요'),
validator: (value) {
if (value.isEmpty) {
return '모델명은 필수입니다';
}
return null;
},
),
const SizedBox(height: 8),
// 상태 메시지 영역 (고정 높이)
SizedBox(
height: 20,
child: _statusMessage != null
? Text(
_statusMessage!,
style: TextStyle(
fontSize: 12,
color: _statusMessage!.contains('존재')
? Colors.red
: Colors.grey,
),
)
: const SizedBox.shrink(),
),
const SizedBox(height: 8),
// 활성 상태는 백엔드에서 관리하므로 UI에서 제거
// 버튼들
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
ShadButton.outline(
onPressed: _isSubmitting ? null : () {
Navigator.of(context).pop();
},
child: const Text('취소'),
),
const SizedBox(width: 8),
ShadButton(
onPressed: _isSubmitting ? null : _handleSubmit,
child: _isSubmitting
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Text(isEditMode ? '수정' : '등록'),
),
],
),
],
),
),
),
);
}
Future<bool> _checkDuplicate() async {
final name = _nameController.text.trim();
if (name.isEmpty) return false;
// 수정 모드일 때 현재 이름과 같으면 검사하지 않음
if (widget.model != null && widget.model!.name == name) {
return false;
}
try {
final isDuplicate = await widget.controller.checkDuplicateName(
name,
excludeId: widget.model?.id,
);
return isDuplicate;
} catch (e) {
// 네트워크 오류 시 false 반환
return false;
}
}
Future<void> _handleSubmit() async {
if (!_formKey.currentState!.validate()) {
return;
}
if (_selectedVendorId == null) {
ShadToaster.of(context).show(
const ShadToast(
title: Text('오류'),
description: Text('제조사를 선택해주세요'),
backgroundColor: Colors.red,
),
);
return;
}
setState(() {
_isSubmitting = true;
_statusMessage = '중복 확인 중...';
});
// 저장 시 중복 검사 수행
final isDuplicate = await _checkDuplicate();
if (isDuplicate) {
setState(() {
_isSubmitting = false;
_statusMessage = '이미 존재하는 모델명입니다.';
});
return;
}
setState(() {
_statusMessage = '저장 중...';
});
bool success;
if (widget.model != null) {
// 수정
success = await widget.controller.updateModel(
id: widget.model!.id,
vendorsId: _selectedVendorId!,
name: _nameController.text,
);
} else {
// 생성
success = await widget.controller.createModel(
vendorsId: _selectedVendorId!,
name: _nameController.text,
);
}
setState(() {
_isSubmitting = false;
_statusMessage = null;
});
if (mounted) {
if (success) {
Navigator.of(context).pop();
ShadToaster.of(context).show(
ShadToast(
title: const Text('성공'),
description: Text(
widget.model != null
? '모델이 수정되었습니다.'
: '모델이 등록되었습니다.',
),
),
);
} else {
ShadToaster.of(context).show(
ShadToast(
title: const Text('오류'),
description: Text(widget.controller.errorMessage ?? '처리 실패'),
backgroundColor: Colors.red,
),
);
}
}
}
}