refactor: 회사 폼 UI 개선 및 코드 정리
- 담당자 연락처 필드를 드롭다운 + 입력 방식으로 분리 - 사용자 폼과 동일한 전화번호 UI 패턴 적용 - 미사용 위젯 파일 4개 정리 (branch_card, contact_info_* 등) - 파일명 통일성 확보 (branch_edit_screen → branch_form, company_form_simplified → company_form) - 네이밍 일관성 개선으로 유지보수성 향상
This commit is contained in:
@@ -17,8 +17,19 @@ class WarehouseLocationFormController extends ChangeNotifier {
|
||||
/// 비고 입력 컨트롤러
|
||||
final TextEditingController remarkController = TextEditingController();
|
||||
|
||||
/// 주소 정보
|
||||
Address _address = const Address();
|
||||
/// 담당자명 입력 컨트롤러
|
||||
final TextEditingController managerNameController = TextEditingController();
|
||||
|
||||
/// 담당자 연락처 입력 컨트롤러
|
||||
final TextEditingController managerPhoneController = TextEditingController();
|
||||
|
||||
/// 수용량 입력 컨트롤러
|
||||
final TextEditingController capacityController = TextEditingController();
|
||||
|
||||
/// 주소 입력 컨트롤러 (단일 필드)
|
||||
final TextEditingController addressController = TextEditingController();
|
||||
|
||||
/// 백엔드 API에 맞는 단순 필드들 (주소는 단일 String)
|
||||
|
||||
/// 저장 중 여부
|
||||
bool _isSaving = false;
|
||||
@@ -53,7 +64,6 @@ class WarehouseLocationFormController extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Getters
|
||||
Address get address => _address;
|
||||
bool get isSaving => _isSaving;
|
||||
bool get isEditMode => _isEditMode;
|
||||
int? get id => _id;
|
||||
@@ -74,8 +84,11 @@ class WarehouseLocationFormController extends ChangeNotifier {
|
||||
|
||||
if (_originalLocation != null) {
|
||||
nameController.text = _originalLocation!.name;
|
||||
_address = _originalLocation!.address;
|
||||
addressController.text = _originalLocation!.address ?? '';
|
||||
remarkController.text = _originalLocation!.remark ?? '';
|
||||
managerNameController.text = _originalLocation!.managerName ?? '';
|
||||
managerPhoneController.text = _originalLocation!.managerPhone ?? '';
|
||||
capacityController.text = _originalLocation!.capacity?.toString() ?? '';
|
||||
}
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
@@ -85,11 +98,6 @@ class WarehouseLocationFormController extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
/// 주소 변경 처리
|
||||
void updateAddress(Address newAddress) {
|
||||
_address = newAddress;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// 저장 처리 (추가/수정)
|
||||
Future<bool> save() async {
|
||||
@@ -103,8 +111,13 @@ class WarehouseLocationFormController extends ChangeNotifier {
|
||||
final location = WarehouseLocation(
|
||||
id: _isEditMode ? _id! : 0,
|
||||
name: nameController.text.trim(),
|
||||
address: _address,
|
||||
address: addressController.text.trim().isEmpty ? null : addressController.text.trim(),
|
||||
remark: remarkController.text.trim().isEmpty ? null : remarkController.text.trim(),
|
||||
managerName: managerNameController.text.trim().isEmpty ? null : managerNameController.text.trim(),
|
||||
managerPhone: managerPhoneController.text.trim().isEmpty ? null : managerPhoneController.text.trim(),
|
||||
capacity: capacityController.text.trim().isEmpty ? null : int.tryParse(capacityController.text.trim()),
|
||||
isActive: true, // 새로 생성 시 항상 활성화
|
||||
createdAt: DateTime.now(),
|
||||
);
|
||||
|
||||
if (_isEditMode) {
|
||||
@@ -127,8 +140,11 @@ class WarehouseLocationFormController extends ChangeNotifier {
|
||||
/// 폼 초기화
|
||||
void resetForm() {
|
||||
nameController.clear();
|
||||
addressController.clear();
|
||||
remarkController.clear();
|
||||
_address = const Address();
|
||||
managerNameController.clear();
|
||||
managerPhoneController.clear();
|
||||
capacityController.clear();
|
||||
_error = null;
|
||||
formKey.currentState?.reset();
|
||||
notifyListeners();
|
||||
@@ -145,9 +161,28 @@ class WarehouseLocationFormController extends ChangeNotifier {
|
||||
return null;
|
||||
}
|
||||
|
||||
String? validateAddress() {
|
||||
if (_address.isEmpty) {
|
||||
return '주소를 입력해주세요';
|
||||
|
||||
/// 수용량 유효성 검사
|
||||
String? validateCapacity(String? value) {
|
||||
if (value != null && value.isNotEmpty) {
|
||||
final capacity = int.tryParse(value);
|
||||
if (capacity == null) {
|
||||
return '올바른 숫자를 입력해주세요';
|
||||
}
|
||||
if (capacity < 0) {
|
||||
return '수용량은 0 이상이어야 합니다';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// 전화번호 유효성 검사
|
||||
String? validatePhoneNumber(String? value) {
|
||||
if (value != null && value.isNotEmpty) {
|
||||
// 기본적인 전화번호 형식 검사 (숫자, 하이픈 허용)
|
||||
if (!RegExp(r'^[0-9-]+$').hasMatch(value)) {
|
||||
return '올바른 전화번호 형식을 입력해주세요';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -156,7 +191,11 @@ class WarehouseLocationFormController extends ChangeNotifier {
|
||||
@override
|
||||
void dispose() {
|
||||
nameController.dispose();
|
||||
addressController.dispose();
|
||||
remarkController.dispose();
|
||||
managerNameController.dispose();
|
||||
managerPhoneController.dispose();
|
||||
capacityController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:superport/models/address_model.dart';
|
||||
import 'package:superport/screens/common/widgets/address_input.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:superport/screens/common/widgets/remark_input.dart';
|
||||
import 'package:superport/screens/common/theme_shadcn.dart';
|
||||
import 'package:superport/screens/common/templates/form_layout_template.dart';
|
||||
@@ -81,47 +80,78 @@ class _WarehouseLocationFormScreenState
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(UIConstants.formPadding),
|
||||
child: FormSection(
|
||||
title: '입고지 정보',
|
||||
subtitle: '입고지의 기본 정보를 입력하세요',
|
||||
title: '창고 정보',
|
||||
subtitle: '창고의 기본 정보를 입력하세요',
|
||||
children: [
|
||||
// 입고지명 입력
|
||||
FormFieldWrapper(
|
||||
label: '입고지명',
|
||||
label: '창고명',
|
||||
required: true,
|
||||
child: TextFormField(
|
||||
controller: _controller.nameController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '입고지명을 입력하세요',
|
||||
hintText: '창고명을 입력하세요',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
validator: (value) {
|
||||
if (value == null || value.trim().isEmpty) {
|
||||
return '입고지명을 입력하세요';
|
||||
return '창고명을 입력하세요';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
),
|
||||
// 주소 입력 (공통 위젯)
|
||||
// 주소 입력 (단일 필드)
|
||||
FormFieldWrapper(
|
||||
label: '주소',
|
||||
required: true,
|
||||
child: AddressInput(
|
||||
initialZipCode: _controller.address.zipCode,
|
||||
initialRegion: _controller.address.region,
|
||||
initialDetailAddress: _controller.address.detailAddress,
|
||||
isRequired: true,
|
||||
onAddressChanged: (zip, region, detail) {
|
||||
setState(() {
|
||||
_controller.updateAddress(
|
||||
Address(
|
||||
zipCode: zip,
|
||||
region: region,
|
||||
detailAddress: detail,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
child: TextFormField(
|
||||
controller: _controller.addressController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '주소를 입력하세요 (예: 경기도 용인시 기흥구 동백로 123)',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
maxLines: 3,
|
||||
),
|
||||
),
|
||||
// 담당자명 입력
|
||||
FormFieldWrapper(
|
||||
label: '담당자명',
|
||||
child: TextFormField(
|
||||
controller: _controller.managerNameController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '담당자명을 입력하세요',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
),
|
||||
// 담당자 연락처 입력
|
||||
FormFieldWrapper(
|
||||
label: '담당자 연락처',
|
||||
child: TextFormField(
|
||||
controller: _controller.managerPhoneController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '연락처를 입력하세요 (예: 02-1234-5678)',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.phone,
|
||||
validator: _controller.validatePhoneNumber,
|
||||
),
|
||||
),
|
||||
// 수용량 입력
|
||||
FormFieldWrapper(
|
||||
label: '수용량',
|
||||
child: TextFormField(
|
||||
controller: _controller.capacityController,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '수용량을 입력하세요 (개)',
|
||||
border: OutlineInputBorder(),
|
||||
suffixText: '개',
|
||||
),
|
||||
keyboardType: TextInputType.number,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
],
|
||||
validator: _controller.validateCapacity,
|
||||
),
|
||||
),
|
||||
// 비고 입력
|
||||
|
||||
@@ -15,7 +15,7 @@ import 'package:superport/services/auth_service.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
import 'package:superport/core/widgets/auth_guard.dart';
|
||||
|
||||
/// shadcn/ui 스타일로 재설계된 입고지 관리 화면
|
||||
/// shadcn/ui 스타일로 재설계된 창고 관리 화면
|
||||
class WarehouseLocationList extends StatefulWidget {
|
||||
const WarehouseLocationList({Key? key}) : super(key: key);
|
||||
|
||||
@@ -62,7 +62,7 @@ class _WarehouseLocationListState
|
||||
_controller.refresh(); // Controller에서 페이지 리셋 처리
|
||||
}
|
||||
|
||||
/// 입고지 추가 폼으로 이동
|
||||
/// 창고 추가 폼으로 이동
|
||||
void _navigateToAdd() async {
|
||||
final result = await Navigator.pushNamed(
|
||||
context,
|
||||
@@ -73,7 +73,7 @@ class _WarehouseLocationListState
|
||||
}
|
||||
}
|
||||
|
||||
/// 입고지 수정 폼으로 이동
|
||||
/// 창고 수정 폼으로 이동
|
||||
void _navigateToEdit(WarehouseLocation location) async {
|
||||
final result = await Navigator.pushNamed(
|
||||
context,
|
||||
@@ -91,7 +91,7 @@ class _WarehouseLocationListState
|
||||
context: context,
|
||||
builder:
|
||||
(context) => AlertDialog(
|
||||
title: const Text('입고지 삭제'),
|
||||
title: const Text('창고 삭제'),
|
||||
content: const Text('정말로 삭제하시겠습니까?'),
|
||||
actions: [
|
||||
TextButton(
|
||||
@@ -110,6 +110,11 @@ class _WarehouseLocationListState
|
||||
);
|
||||
}
|
||||
|
||||
/// 날짜 포맷팅 함수
|
||||
String _formatDate(DateTime date) {
|
||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}';
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// Admin과 Manager만 접근 가능
|
||||
@@ -130,13 +135,13 @@ class _WarehouseLocationListState
|
||||
emptyMessage:
|
||||
controller.searchQuery.isNotEmpty
|
||||
? '검색 결과가 없습니다'
|
||||
: '등록된 입고지가 없습니다',
|
||||
: '등록된 창고가 없습니다',
|
||||
emptyIcon: Icons.warehouse_outlined,
|
||||
|
||||
// 검색바
|
||||
searchBar: UnifiedSearchBar(
|
||||
controller: _searchController,
|
||||
placeholder: '창고명, 주소로 검색',
|
||||
placeholder: '창고명, 주소, 담당자로 검색',
|
||||
onChanged: (value) => _controller.search(value),
|
||||
onSearch: () => _controller.search(_searchController.text),
|
||||
onClear: () {
|
||||
@@ -149,7 +154,7 @@ class _WarehouseLocationListState
|
||||
actionBar: StandardActionBar(
|
||||
leftActions: [
|
||||
ShadcnButton(
|
||||
text: '입고지 추가',
|
||||
text: '창고 추가',
|
||||
onPressed: _navigateToAdd,
|
||||
variant: ShadcnButtonVariant.primary,
|
||||
textColor: Colors.white,
|
||||
@@ -211,7 +216,7 @@ class _WarehouseLocationListState
|
||||
action:
|
||||
_controller.searchQuery.isEmpty
|
||||
? StandardActionButtons.addButton(
|
||||
text: '첫 입고지 추가하기',
|
||||
text: '첫 창고 추가하기',
|
||||
onPressed: _navigateToAdd,
|
||||
)
|
||||
: null,
|
||||
@@ -246,16 +251,32 @@ class _WarehouseLocationListState
|
||||
child: Text('번호', style: ShadcnTheme.bodyMedium),
|
||||
),
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Text('입고지명', style: ShadcnTheme.bodyMedium),
|
||||
flex: 2,
|
||||
child: Text('창고명', style: ShadcnTheme.bodyMedium),
|
||||
),
|
||||
Expanded(
|
||||
flex: 4,
|
||||
flex: 3,
|
||||
child: Text('주소', style: ShadcnTheme.bodyMedium),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text('비고', style: ShadcnTheme.bodyMedium),
|
||||
child: Text('담당자', style: ShadcnTheme.bodyMedium),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text('연락처', style: ShadcnTheme.bodyMedium),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text('수용량', style: ShadcnTheme.bodyMedium),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text('상태', style: ShadcnTheme.bodyMedium),
|
||||
),
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text('생성일', style: ShadcnTheme.bodyMedium),
|
||||
),
|
||||
Expanded(
|
||||
flex: 1,
|
||||
@@ -290,28 +311,85 @@ class _WarehouseLocationListState
|
||||
style: ShadcnTheme.bodySmall,
|
||||
),
|
||||
),
|
||||
// 입고지명
|
||||
// 창고명
|
||||
Expanded(
|
||||
flex: 3,
|
||||
flex: 2,
|
||||
child: Text(
|
||||
location.name,
|
||||
style: ShadcnTheme.bodyMedium,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
// 주소
|
||||
Expanded(
|
||||
flex: 4,
|
||||
flex: 3,
|
||||
child: Text(
|
||||
'${location.address.region} ${location.address.detailAddress}',
|
||||
location.address ?? '-',
|
||||
style: ShadcnTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
// 비고
|
||||
// 담당자
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
location.remark ?? '-',
|
||||
location.managerName ?? '-',
|
||||
style: ShadcnTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
// 연락처
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
location.managerPhone ?? '-',
|
||||
style: ShadcnTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
// 수용량
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Text(
|
||||
location.capacity?.toString() ?? '-',
|
||||
style: ShadcnTheme.bodySmall,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
// 상태
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: location.isActive
|
||||
? ShadcnTheme.success.withOpacity(0.1)
|
||||
: ShadcnTheme.muted.withOpacity(0.1),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: location.isActive
|
||||
? ShadcnTheme.success.withOpacity(0.3)
|
||||
: ShadcnTheme.muted.withOpacity(0.3)
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
location.isActive ? '활성' : '비활성',
|
||||
style: TextStyle(
|
||||
fontSize: 10,
|
||||
color: location.isActive
|
||||
? ShadcnTheme.success
|
||||
: ShadcnTheme.muted,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
),
|
||||
// 생성일
|
||||
Expanded(
|
||||
flex: 2,
|
||||
child: Text(
|
||||
_formatDate(location.createdAt),
|
||||
style: ShadcnTheme.bodySmall,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user