사용하지 않는 파일 정리 전 백업 (Phase 10 완료 후 상태)
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:superport/screens/common/theme_shadcn.dart';
|
||||
import 'package:superport/utils/constants.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
import 'package:superport/utils/validators.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:superport/screens/user/controllers/user_form_controller.dart';
|
||||
import 'package:superport/models/user_model.dart';
|
||||
import 'package:superport/utils/formatters/korean_phone_formatter.dart';
|
||||
|
||||
// 사용자 등록/수정 화면 (UI만 담당, 상태/로직 분리)
|
||||
class UserFormScreen extends StatefulWidget {
|
||||
@@ -42,7 +42,7 @@ class _UserFormScreenState extends State<UserFormScreen> {
|
||||
title: Text(controller.isEditMode ? '사용자 수정' : '사용자 등록'),
|
||||
),
|
||||
body: controller.isLoading
|
||||
? const Center(child: CircularProgressIndicator())
|
||||
? const Center(child: ShadProgress())
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Form(
|
||||
@@ -89,9 +89,7 @@ class _UserFormScreenState extends State<UserFormScreen> {
|
||||
height: 20,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(12.0),
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
child: ShadProgress(),
|
||||
),
|
||||
)
|
||||
: controller.isUsernameAvailable != null
|
||||
@@ -153,45 +151,52 @@ class _UserFormScreenState extends State<UserFormScreen> {
|
||||
],
|
||||
|
||||
// 수정 모드에서 비밀번호 변경 (선택사항)
|
||||
if (controller.isEditMode) ...[
|
||||
ExpansionTile(
|
||||
title: const Text('비밀번호 변경'),
|
||||
if (controller.isEditMode) ...[
|
||||
ShadAccordion<int>(
|
||||
children: [
|
||||
_buildPasswordField(
|
||||
label: '새 비밀번호',
|
||||
controller: _passwordController,
|
||||
hintText: '변경할 경우만 입력하세요',
|
||||
obscureText: !_showPassword,
|
||||
onToggleVisibility: () {
|
||||
setState(() {
|
||||
_showPassword = !_showPassword;
|
||||
});
|
||||
},
|
||||
validator: (value) {
|
||||
if (value != null && value.isNotEmpty && value.length < 6) {
|
||||
return '비밀번호는 6자 이상이어야 합니다';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onSaved: (value) => controller.password = value ?? '',
|
||||
),
|
||||
|
||||
_buildPasswordField(
|
||||
label: '새 비밀번호 확인',
|
||||
controller: _confirmPasswordController,
|
||||
hintText: '비밀번호를 다시 입력하세요',
|
||||
obscureText: !_showConfirmPassword,
|
||||
onToggleVisibility: () {
|
||||
setState(() {
|
||||
_showConfirmPassword = !_showConfirmPassword;
|
||||
});
|
||||
},
|
||||
validator: (value) {
|
||||
if (_passwordController.text.isNotEmpty && value != _passwordController.text) {
|
||||
return '비밀번호가 일치하지 않습니다';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
ShadAccordionItem(
|
||||
value: 1,
|
||||
title: const Text('비밀번호 변경'),
|
||||
child: Column(
|
||||
children: [
|
||||
_buildPasswordField(
|
||||
label: '새 비밀번호',
|
||||
controller: _passwordController,
|
||||
hintText: '변경할 경우만 입력하세요',
|
||||
obscureText: !_showPassword,
|
||||
onToggleVisibility: () {
|
||||
setState(() {
|
||||
_showPassword = !_showPassword;
|
||||
});
|
||||
},
|
||||
validator: (value) {
|
||||
if (value != null && value.isNotEmpty && value.length < 6) {
|
||||
return '비밀번호는 6자 이상이어야 합니다';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onSaved: (value) => controller.password = value ?? '',
|
||||
),
|
||||
|
||||
_buildPasswordField(
|
||||
label: '새 비밀번호 확인',
|
||||
controller: _confirmPasswordController,
|
||||
hintText: '비밀번호를 다시 입력하세요',
|
||||
obscureText: !_showConfirmPassword,
|
||||
onToggleVisibility: () {
|
||||
setState(() {
|
||||
_showConfirmPassword = !_showConfirmPassword;
|
||||
});
|
||||
},
|
||||
validator: (value) {
|
||||
if (_passwordController.text.isNotEmpty && value != _passwordController.text) {
|
||||
return '비밀번호가 일치하지 않습니다';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -221,54 +226,22 @@ class _UserFormScreenState extends State<UserFormScreen> {
|
||||
const SizedBox(height: 24),
|
||||
// 오류 메시지 표시
|
||||
if (controller.error != null)
|
||||
Container(
|
||||
padding: const EdgeInsets.all(12),
|
||||
margin: const EdgeInsets.only(bottom: 16),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.shade50,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(color: Colors.red.shade200),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(Icons.error_outline, color: Colors.red.shade700),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
controller.error!,
|
||||
style: TextStyle(color: Colors.red.shade700),
|
||||
),
|
||||
),
|
||||
],
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: ShadAlert.destructive(
|
||||
title: const Text('오류'),
|
||||
description: Text(controller.error!),
|
||||
),
|
||||
),
|
||||
// 저장 버튼
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
child: ElevatedButton(
|
||||
child: ShadButton(
|
||||
onPressed: controller.isLoading
|
||||
? null
|
||||
: () => _onSaveUser(controller),
|
||||
style: ElevatedButton.styleFrom(
|
||||
backgroundColor: ShadcnTheme.primary,
|
||||
foregroundColor: Colors.white,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: controller.isLoading
|
||||
? const SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
)
|
||||
: Text(
|
||||
controller.isEditMode ? '수정하기' : '등록하기',
|
||||
style: const TextStyle(fontSize: 16),
|
||||
),
|
||||
),
|
||||
size: ShadButtonSize.lg,
|
||||
child: Text(controller.isEditMode ? '수정하기' : '등록하기'),
|
||||
),
|
||||
),
|
||||
],
|
||||
@@ -294,6 +267,7 @@ class _UserFormScreenState extends State<UserFormScreen> {
|
||||
void Function(String)? onChanged,
|
||||
Widget? suffixIcon,
|
||||
}) {
|
||||
final controller = TextEditingController(text: initialValue);
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
child: Column(
|
||||
@@ -301,12 +275,9 @@ class _UserFormScreenState extends State<UserFormScreen> {
|
||||
children: [
|
||||
Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 4),
|
||||
TextFormField(
|
||||
initialValue: initialValue,
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
suffixIcon: suffixIcon,
|
||||
),
|
||||
ShadInputFormField(
|
||||
controller: controller,
|
||||
placeholder: Text(hintText),
|
||||
keyboardType: keyboardType,
|
||||
inputFormatters: inputFormatters,
|
||||
validator: validator,
|
||||
@@ -335,18 +306,10 @@ class _UserFormScreenState extends State<UserFormScreen> {
|
||||
children: [
|
||||
Text(label, style: const TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 4),
|
||||
TextFormField(
|
||||
ShadInputFormField(
|
||||
controller: controller,
|
||||
obscureText: obscureText,
|
||||
decoration: InputDecoration(
|
||||
hintText: hintText,
|
||||
suffixIcon: IconButton(
|
||||
icon: Icon(
|
||||
obscureText ? Icons.visibility : Icons.visibility_off,
|
||||
),
|
||||
onPressed: onToggleVisibility,
|
||||
),
|
||||
),
|
||||
placeholder: Text(hintText),
|
||||
validator: validator,
|
||||
onSaved: onSaved,
|
||||
),
|
||||
@@ -355,7 +318,7 @@ class _UserFormScreenState extends State<UserFormScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
// 전화번호 입력 섹션 (드롭다운 + 텍스트 필드)
|
||||
// 전화번호 입력 섹션 (통합 입력 필드)
|
||||
Widget _buildPhoneNumberSection(UserFormController controller) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16.0),
|
||||
@@ -364,78 +327,28 @@ class _UserFormScreenState extends State<UserFormScreen> {
|
||||
children: [
|
||||
const Text('전화번호', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
// 접두사 드롭다운 (010, 02, 031 등)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: Colors.grey),
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
),
|
||||
child: DropdownButton<String>(
|
||||
value: controller.phonePrefix,
|
||||
items: controller.phonePrefixes.map((prefix) {
|
||||
return DropdownMenuItem(
|
||||
value: prefix,
|
||||
child: Text(prefix),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
controller.updatePhonePrefix(value);
|
||||
}
|
||||
},
|
||||
underline: Container(), // 밑줄 제거
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
const Text('-', style: TextStyle(fontSize: 16)),
|
||||
const SizedBox(width: 8),
|
||||
// 전화번호 입력 (7-8자리)
|
||||
Expanded(
|
||||
child: TextFormField(
|
||||
initialValue: controller.phoneNumber,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '1234567 또는 12345678',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
keyboardType: TextInputType.phone,
|
||||
inputFormatters: [
|
||||
FilteringTextInputFormatter.digitsOnly,
|
||||
LengthLimitingTextInputFormatter(8),
|
||||
],
|
||||
validator: (value) {
|
||||
if (value != null && value.isNotEmpty) {
|
||||
if (value.length < 7 || value.length > 8) {
|
||||
return '전화번호는 7-8자리 숫자를 입력해주세요';
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
onChanged: (value) {
|
||||
controller.updatePhoneNumber(value);
|
||||
},
|
||||
onSaved: (value) {
|
||||
if (value != null) {
|
||||
controller.updatePhoneNumber(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
ShadInputFormField(
|
||||
controller: TextEditingController(text: controller.combinedPhoneNumber),
|
||||
placeholder: const Text('010-1234-5678'),
|
||||
keyboardType: TextInputType.phone,
|
||||
inputFormatters: [
|
||||
KoreanPhoneFormatter(), // 한국식 전화번호 자동 포맷팅
|
||||
],
|
||||
validator: (value) {
|
||||
if (value.isNotEmpty) {
|
||||
return PhoneValidator.validate(value);
|
||||
}
|
||||
return null; // 선택 필드이므로 비어있어도 OK
|
||||
},
|
||||
onChanged: (value) {
|
||||
controller.updatePhoneNumber(value);
|
||||
},
|
||||
onSaved: (value) {
|
||||
if (value != null) {
|
||||
controller.updatePhoneNumber(value);
|
||||
}
|
||||
},
|
||||
),
|
||||
if (controller.combinedPhoneNumber.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(top: 4),
|
||||
child: Text(
|
||||
'전화번호: ${controller.combinedPhoneNumber}',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: Colors.grey[600],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -450,14 +363,11 @@ class _UserFormScreenState extends State<UserFormScreen> {
|
||||
children: [
|
||||
const Text('권한 *', style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
const SizedBox(height: 4),
|
||||
DropdownButtonFormField<UserRole>(
|
||||
value: controller.role,
|
||||
decoration: const InputDecoration(
|
||||
hintText: '권한을 선택하세요',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
items: UserRole.values.map((role) {
|
||||
return DropdownMenuItem<UserRole>(
|
||||
ShadSelect<UserRole>(
|
||||
selectedOptionBuilder: (context, value) => Text(value.displayName ?? ''),
|
||||
placeholder: const Text('권한을 선택하세요'),
|
||||
options: UserRole.values.map((role) {
|
||||
return ShadOption(
|
||||
value: role,
|
||||
child: Text(role.displayName),
|
||||
);
|
||||
@@ -467,12 +377,6 @@ class _UserFormScreenState extends State<UserFormScreen> {
|
||||
controller.role = value;
|
||||
}
|
||||
},
|
||||
validator: (value) {
|
||||
if (value == null) {
|
||||
return '권한을 선택해주세요';
|
||||
}
|
||||
return null;
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
@@ -494,17 +398,19 @@ class _UserFormScreenState extends State<UserFormScreen> {
|
||||
void _onSaveUser(UserFormController controller) async {
|
||||
await controller.saveUser((error) {
|
||||
if (error != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(error),
|
||||
backgroundColor: Colors.red,
|
||||
ShadToaster.of(context).show(
|
||||
ShadToast.destructive(
|
||||
title: const Text('오류'),
|
||||
description: Text(error),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(controller.isEditMode ? '사용자 정보가 수정되었습니다' : '사용자가 등록되었습니다'),
|
||||
backgroundColor: Colors.green,
|
||||
ShadToaster.of(context).show(
|
||||
ShadToast(
|
||||
title: const Text('성공'),
|
||||
description: Text(
|
||||
controller.isEditMode ? '사용자 정보가 수정되었습니다' : '사용자가 등록되었습니다',
|
||||
),
|
||||
),
|
||||
);
|
||||
Navigator.pop(context, true);
|
||||
|
||||
Reference in New Issue
Block a user