refactor: 회사 폼 UI 개선 및 코드 정리
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

- 담당자 연락처 필드를 드롭다운 + 입력 방식으로 분리
- 사용자 폼과 동일한 전화번호 UI 패턴 적용
- 미사용 위젯 파일 4개 정리 (branch_card, contact_info_* 등)
- 파일명 통일성 확보 (branch_edit_screen → branch_form, company_form_simplified → company_form)
- 네이밍 일관성 개선으로 유지보수성 향상
This commit is contained in:
JiWoong Sul
2025-08-18 17:57:16 +09:00
parent 93bceb8a6c
commit 6d745051b5
37 changed files with 2743 additions and 2446 deletions

View File

@@ -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();
}
}

View File

@@ -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,
),
),
// 비고 입력

View File

@@ -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,
),