Files
superport/lib/screens/model/model_form_dialog.dart
JiWoong Sul df7dd8dacb
Some checks failed
Flutter Test & Quality Check / Test on macos-latest (push) Has been cancelled
Flutter Test & Quality Check / Test on ubuntu-latest (push) Has been cancelled
Flutter Test & Quality Check / Build APK (push) Has been cancelled
feat: 대규모 코드베이스 개선 - 백엔드 통합성 강화 및 UI 일관성 완성
- CLAUDE.md 대폭 개선: 개발 가이드라인 및 프로젝트 상태 문서화
- 백엔드 API 통합: 모든 엔티티 간 Foreign Key 관계 완벽 구현
- UI 일관성 강화: shadcn_ui 컴포넌트 표준화 적용
- 데이터 모델 개선: DTO 및 모델 클래스 백엔드 스키마와 100% 일치
- 사용자 관리: 회사 연결, 중복 검사, 입력 검증 기능 추가
- 창고 관리: 우편번호 연결, 중복 검사 기능 강화
- 회사 관리: 우편번호 연결, 중복 검사 로직 구현
- 장비 관리: 불필요한 카테고리 필드 제거, 벤더-모델 관계 정리
- 우편번호 시스템: 검색 다이얼로그 Provider 버그 수정

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-31 15:49:05 +09:00

246 lines
6.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:shadcn_ui/shadcn_ui.dart';
import 'package:superport/data/models/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: [
// Vendor 선택
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,
),
);
}
}
}
}