- Remove copy buttons in ZipcodeTable (zipcode and full address) - Drop unused clipboard helper and import - Use tableMaxWidthFraction: 0.33 in Company/Warehouse zipcode dialogs
258 lines
8.5 KiB
Dart
258 lines
8.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:shadcn_ui/shadcn_ui.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:get_it/get_it.dart';
|
|
import 'package:superport/screens/common/widgets/remark_input.dart';
|
|
import 'package:superport/screens/common/templates/form_layout_template.dart';
|
|
import 'controllers/warehouse_location_form_controller.dart';
|
|
import 'package:superport/data/models/zipcode_dto.dart';
|
|
import 'package:superport/screens/zipcode/zipcode_search_screen.dart';
|
|
import 'package:superport/screens/zipcode/controllers/zipcode_controller.dart';
|
|
import 'package:superport/domain/usecases/zipcode_usecase.dart';
|
|
|
|
/// 입고지 추가/수정 폼 화면 (SRP 적용, 상태/로직 분리)
|
|
class WarehouseLocationFormScreen extends StatefulWidget {
|
|
final int? id; // 수정 모드 지원을 위한 id 파라미터
|
|
final Map<String, dynamic>? preloadedData; // 사전 로드된 데이터
|
|
const WarehouseLocationFormScreen({super.key, this.id, this.preloadedData});
|
|
|
|
@override
|
|
State<WarehouseLocationFormScreen> createState() =>
|
|
_WarehouseLocationFormScreenState();
|
|
}
|
|
|
|
class _WarehouseLocationFormScreenState
|
|
extends State<WarehouseLocationFormScreen> {
|
|
/// 폼 컨트롤러 (상태 및 저장/수정 로직 위임)
|
|
late final WarehouseLocationFormController _controller;
|
|
|
|
/// 상태 메시지
|
|
String? _statusMessage;
|
|
|
|
/// 저장 중 여부
|
|
bool _isSaving = false;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// 컨트롤러 생성 및 초기화
|
|
if (widget.preloadedData != null) {
|
|
// 사전 로드된 데이터로 즉시 초기화
|
|
_controller = WarehouseLocationFormController.withPreloadedData(
|
|
preloadedData: widget.preloadedData!,
|
|
);
|
|
} else {
|
|
_controller = WarehouseLocationFormController();
|
|
if (widget.id != null) {
|
|
// 비동기 초기화를 위해 addPostFrameCallback 사용
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
_controller.initialize(widget.id!);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
// 컨트롤러 해제
|
|
_controller.dispose();
|
|
super.dispose();
|
|
}
|
|
|
|
// 우편번호 검색 다이얼로그
|
|
Future<ZipcodeDto?> _showZipcodeSearchDialog() async {
|
|
return await showDialog<ZipcodeDto>(
|
|
context: context,
|
|
barrierDismissible: true,
|
|
builder: (BuildContext dialogContext) => Dialog(
|
|
clipBehavior: Clip.none,
|
|
insetPadding: const EdgeInsets.symmetric(horizontal: 40, vertical: 24),
|
|
child: SizedBox(
|
|
width: 800,
|
|
height: 600,
|
|
child: Container(
|
|
decoration: BoxDecoration(
|
|
color: ShadTheme.of(context).colorScheme.background,
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
child: ChangeNotifierProvider(
|
|
create: (_) => ZipcodeController(
|
|
GetIt.instance<ZipcodeUseCase>(),
|
|
),
|
|
child: ZipcodeSearchScreen(
|
|
tableMaxWidthFraction: 0.33,
|
|
onSelect: (zipcode) {
|
|
Navigator.of(dialogContext).pop(zipcode);
|
|
},
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
// 저장 메소드
|
|
Future<void> _onSave() async {
|
|
// 폼 유효성 검사
|
|
if (!_controller.formKey.currentState!.validate()) {
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_isSaving = true;
|
|
_statusMessage = '중복 확인 중...';
|
|
});
|
|
|
|
// 저장 시 중복 검사 수행
|
|
final name = _controller.nameController.text.trim();
|
|
final isDuplicate = await _controller.checkDuplicateName(
|
|
name,
|
|
excludeId: _controller.isEditMode ? _controller.id : null,
|
|
);
|
|
|
|
if (isDuplicate) {
|
|
setState(() {
|
|
_isSaving = false;
|
|
_statusMessage = '이미 존재하는 창고명입니다.';
|
|
});
|
|
return;
|
|
}
|
|
|
|
setState(() {
|
|
_statusMessage = '저장 중...';
|
|
});
|
|
|
|
final success = await _controller.save();
|
|
|
|
setState(() {
|
|
_isSaving = false;
|
|
_statusMessage = null;
|
|
});
|
|
|
|
if (success) {
|
|
// 성공 메시지 표시
|
|
if (mounted) {
|
|
ShadToaster.of(context).show(
|
|
ShadToast(
|
|
title: const Text('성공'),
|
|
description: Text(_controller.isEditMode ? '입고지가 수정되었습니다' : '입고지가 추가되었습니다'),
|
|
),
|
|
);
|
|
// 리스트 화면으로 돌아가기
|
|
Navigator.of(context).pop(true);
|
|
}
|
|
} else {
|
|
// 실패 메시지 표시
|
|
if (mounted) {
|
|
ShadToaster.of(context).show(
|
|
ShadToast.destructive(
|
|
title: const Text('오류'),
|
|
description: Text(_controller.error ?? '저장에 실패했습니다'),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return FormLayoutTemplate(
|
|
title: _controller.isEditMode ? '입고지 수정' : '입고지 추가',
|
|
onSave: _isSaving ? null : _onSave,
|
|
saveButtonText: '저장',
|
|
isLoading: _isSaving,
|
|
child: Form(
|
|
key: _controller.formKey,
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(UIConstants.formPadding),
|
|
child: FormSection(
|
|
title: '창고 정보',
|
|
subtitle: '창고의 기본 정보를 입력하세요',
|
|
children: [
|
|
// 입고지명 입력
|
|
FormFieldWrapper(
|
|
label: '창고명',
|
|
required: true,
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
ShadInputFormField(
|
|
controller: _controller.nameController,
|
|
placeholder: const Text('창고명을 입력하세요'),
|
|
validator: (value) {
|
|
if (value.trim().isEmpty) {
|
|
return '창고명을 입력하세요';
|
|
}
|
|
return null;
|
|
},
|
|
),
|
|
// 상태 메시지 영역 (고정 높이)
|
|
SizedBox(
|
|
height: 20,
|
|
child: _statusMessage != null
|
|
? Padding(
|
|
padding: const EdgeInsets.only(top: 4),
|
|
child: Text(
|
|
_statusMessage!,
|
|
style: TextStyle(
|
|
fontSize: 12,
|
|
color: _statusMessage!.contains('존재')
|
|
? Colors.red
|
|
: Colors.grey,
|
|
),
|
|
),
|
|
)
|
|
: const SizedBox.shrink(),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
// 우편번호 검색
|
|
FormFieldWrapper(
|
|
label: '우편번호',
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: ShadInputFormField(
|
|
controller: _controller.zipcodeController,
|
|
placeholder: const Text('우편번호'),
|
|
readOnly: true,
|
|
),
|
|
),
|
|
const SizedBox(width: 8),
|
|
ShadButton(
|
|
onPressed: () async {
|
|
// 우편번호 검색 다이얼로그 호출
|
|
final result = await _showZipcodeSearchDialog();
|
|
if (result != null) {
|
|
_controller.selectZipcode(result);
|
|
}
|
|
},
|
|
child: const Text('검색'),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
// 주소 입력 (단일 필드)
|
|
FormFieldWrapper(
|
|
label: '주소',
|
|
child: ShadInputFormField(
|
|
controller: _controller.addressController,
|
|
placeholder: const Text('상세 주소를 입력하세요'),
|
|
maxLines: 2,
|
|
),
|
|
),
|
|
// 비고 입력
|
|
FormFieldWrapper(
|
|
label: '비고',
|
|
child: RemarkInput(controller: _controller.remarkController),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|