import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import 'dart:async'; import '../../../../core/services/auth_proxy_service.dart'; import '../../../../core/i18n/locale_utils.dart'; import '../../../../core/ui/toast_service.dart'; class UserManagementScreen extends StatefulWidget { const UserManagementScreen({super.key}); @override State createState() => _UserManagementScreenState(); } class _UserManagementScreenState extends State with SingleTickerProviderStateMixin { late TabController _tabController; bool _isAuthorized = false; String? _verifiedAdminPassword; bool _isLoading = false; // --- List Tab Variables --- List _users = []; final TextEditingController _searchController = TextEditingController(); Timer? _debounce; // --- Create Tab Controllers --- final _formKey = GlobalKey(); final TextEditingController _createLoginIdController = TextEditingController(); final TextEditingController _createEmailController = TextEditingController(); final TextEditingController _createPhoneController = TextEditingController(); final TextEditingController _createNameController = TextEditingController(); @override void initState() { super.initState(); _tabController = TabController(length: 2, vsync: this); WidgetsBinding.instance.addPostFrameCallback((_) => _verifyAccess()); } @override void dispose() { _tabController.dispose(); _searchController.dispose(); _debounce?.cancel(); _createLoginIdController.dispose(); _createEmailController.dispose(); _createPhoneController.dispose(); _createNameController.dispose(); super.dispose(); } // --- Authentication --- Future _verifyAccess() async { final passwordController = TextEditingController(); final String? inputPassword = await showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: const Text("Admin Authentication Required"), content: Column( mainAxisSize: MainAxisSize.min, children: [ const Text("Please enter the admin password."), const SizedBox(height: 16), TextField( controller: passwordController, obscureText: true, decoration: const InputDecoration( labelText: "Password", border: OutlineInputBorder(), ), autofocus: true, onSubmitted: (value) => Navigator.pop(context, value), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context, null), child: const Text("Cancel"), ), FilledButton( onPressed: () => Navigator.pop(context, passwordController.text), child: const Text("Enter"), ), ], ), ); if (inputPassword == null || inputPassword.isEmpty) { if (mounted) context.go(buildLocalizedHomePath(Uri.base)); return; } setState(() => _isLoading = true); final isValid = await AuthProxyService.checkAdminAuth(inputPassword); setState(() => _isLoading = false); if (isValid) { if (mounted) { setState(() { _isAuthorized = true; _verifiedAdminPassword = inputPassword; }); _loadUsers(); } } else { if (mounted) { ToastService.error('Invalid Password'); context.go(buildLocalizedHomePath(Uri.base)); } } } // --- User List Logic --- Future _loadUsers({String? query}) async { if (_verifiedAdminPassword == null) return; setState(() => _isLoading = true); try { final users = await AuthProxyService.listUsers( _verifiedAdminPassword!, query: query, ); setState(() => _users = users); } catch (e) { _showError("Failed to load users: $e"); } finally { if (mounted) setState(() => _isLoading = false); } } void _onSearchChanged(String query) { if (_debounce?.isActive ?? false) _debounce!.cancel(); _debounce = Timer(const Duration(milliseconds: 500), () { _loadUsers(query: query); }); } Future _deleteUser(String loginId) async { if (_verifiedAdminPassword == null) return; final confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: const Text("Delete User"), content: Text( "Are you sure you want to delete $loginId? This cannot be undone.", ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text("Cancel"), ), FilledButton( style: FilledButton.styleFrom(backgroundColor: Colors.red), onPressed: () => Navigator.pop(context, true), child: const Text("Delete"), ), ], ), ); if (confirm != true) return; setState(() => _isLoading = true); try { await AuthProxyService.deleteUser(_verifiedAdminPassword!, loginId); _showSuccess("User deleted"); _loadUsers(query: _searchController.text); } catch (e) { _showError("Failed to delete: $e"); } finally { if (mounted) setState(() => _isLoading = false); } } Future _toggleStatus(String loginId, String currentStatus) async { if (_verifiedAdminPassword == null) return; final newStatus = (currentStatus == "enabled" || currentStatus == "active") ? "disabled" : "enabled"; setState(() => _isLoading = true); try { await AuthProxyService.updateUserStatus( _verifiedAdminPassword!, loginId, newStatus, ); _showSuccess("User status updated to $newStatus"); _loadUsers(query: _searchController.text); } catch (e) { _showError("Failed to update status: $e"); } finally { if (mounted) setState(() => _isLoading = false); } } Future _editUser(Map user) async { if (_verifiedAdminPassword == null) return; final loginIDs = (user['loginIds'] as List?) ?? []; final loginId = loginIDs.isNotEmpty ? loginIDs.first.toString() : ""; if (loginId.isEmpty) return; final nameController = TextEditingController( text: user['name'] ?? user['user']?['name'] ?? "", ); final emailController = TextEditingController( text: user['user']?['email'] ?? "", ); final phoneController = TextEditingController( text: user['user']?['phone'] ?? "", ); final confirm = await showDialog( context: context, builder: (context) => AlertDialog( title: Text("Edit User: $loginId"), content: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: nameController, decoration: const InputDecoration(labelText: "Name"), ), TextField( controller: emailController, decoration: const InputDecoration(labelText: "Email"), ), TextField( controller: phoneController, decoration: const InputDecoration(labelText: "Phone"), ), ], ), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text("Cancel"), ), FilledButton( onPressed: () => Navigator.pop(context, true), child: const Text("Save"), ), ], ), ); if (confirm != true) return; setState(() => _isLoading = true); String? phone = phoneController.text.trim().isEmpty ? null : phoneController.text.trim(); if (phone != null && !phone.contains('@')) { phone = phone.replaceAll(RegExp(r'[-\s]'), ''); if (phone.startsWith('010')) { phone = '+82${phone.substring(1)}'; } } try { await AuthProxyService.updateUserDetails( adminPassword: _verifiedAdminPassword!, loginId: loginId, displayName: nameController.text.trim(), email: emailController.text.trim(), phone: phone, ); _showSuccess("User updated successfully"); _loadUsers(query: _searchController.text); } catch (e) { _showError("Update failed: $e"); } finally { if (mounted) setState(() => _isLoading = false); } } // --- Create User Logic --- Future _createUserSubmit() async { if (!_formKey.currentState!.validate()) return; if (_verifiedAdminPassword == null) return; setState(() => _isLoading = true); String loginId = _createLoginIdController.text.trim(); if (!loginId.contains('@')) { loginId = loginId.replaceAll(RegExp(r'[-\s]'), ''); if (loginId.startsWith('010')) { loginId = '+82${loginId.substring(1)}'; } } String? phone = _createPhoneController.text.trim().isEmpty ? null : _createPhoneController.text.trim(); if (phone != null && !phone.contains('@')) { phone = phone.replaceAll(RegExp(r'[-\s]'), ''); if (phone.startsWith('010')) { phone = '+82${phone.substring(1)}'; } } try { await AuthProxyService.createUser( loginId: loginId, adminPassword: _verifiedAdminPassword!, email: _createEmailController.text.trim().isEmpty ? null : _createEmailController.text.trim(), phone: phone, displayName: _createNameController.text.trim().isEmpty ? null : _createNameController.text.trim(), ); _showSuccess("User created successfully"); _formKey.currentState!.reset(); _createLoginIdController.clear(); _createEmailController.clear(); _createPhoneController.clear(); _createNameController.clear(); // Switch to list tab and reload _tabController.animateTo(0); _loadUsers(); } catch (e) { _showError("Error: $e"); } finally { if (mounted) setState(() => _isLoading = false); } } // --- UI Helpers --- void _showError(String msg) { if (!mounted) return; ToastService.error(msg); } void _showSuccess(String msg) { if (!mounted) return; ToastService.success(msg); } @override Widget build(BuildContext context) { if (!_isAuthorized) { return const Scaffold(body: Center(child: CircularProgressIndicator())); } return Scaffold( appBar: AppBar( title: const Text('User Management'), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.go(buildLocalizedHomePath(Uri.base)), ), bottom: TabBar( controller: _tabController, tabs: const [ Tab(icon: Icon(Icons.list), text: "User List"), Tab(icon: Icon(Icons.person_add), text: "Create User"), ], ), ), body: TabBarView( controller: _tabController, children: [_buildUserListTab(), _buildCreateUserTab()], ), ); } Widget _buildUserListTab() { return Column( children: [ if (_isLoading) const LinearProgressIndicator(), Padding( padding: const EdgeInsets.all(16.0), child: TextField( controller: _searchController, decoration: const InputDecoration( labelText: "Search Users", prefixIcon: Icon(Icons.search), border: OutlineInputBorder(), hintText: "Email, Phone, or Name", ), onChanged: _onSearchChanged, ), ), Expanded( child: _users.isEmpty ? const Center(child: Text("No users found.")) : ListView.separated( itemCount: _users.length, separatorBuilder: (context, index) => const Divider(), itemBuilder: (context, index) { final user = _users[index]; // 일부 응답은 최상위 또는 user 하위에 필드를 포함합니다. final loginIDs = (user['loginIds'] as List?) ?? []; final loginId = loginIDs.isNotEmpty ? loginIDs.first.toString() : "Unknown ID"; final name = user['name'] ?? user['user']?['name'] ?? "No Name"; final status = user['status'] ?? "unknown"; final isEnabled = status == "enabled" || status == "active"; return ListTile( leading: CircleAvatar( backgroundColor: isEnabled ? Colors.green.shade100 : Colors.grey.shade300, child: Icon( Icons.person, color: isEnabled ? Colors.green : Colors.grey, ), ), title: Text(name), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( loginId, style: const TextStyle(fontWeight: FontWeight.bold), ), Text( "Status: $status", style: TextStyle( color: isEnabled ? Colors.green : Colors.red, fontSize: 12, ), ), ], ), trailing: Row( mainAxisSize: MainAxisSize.min, children: [ IconButton( icon: const Icon(Icons.edit, color: Colors.blue), tooltip: "Edit User", onPressed: () => _editUser(user), ), IconButton( icon: Icon( isEnabled ? Icons.block : Icons.check_circle, color: isEnabled ? Colors.orange : Colors.green, ), tooltip: isEnabled ? "Disable User" : "Enable User", onPressed: () => _toggleStatus(loginId, status), ), IconButton( icon: const Icon(Icons.delete, color: Colors.red), tooltip: "Delete User", onPressed: () => _deleteUser(loginId), ), ], ), ); }, ), ), ], ); } Widget _buildCreateUserTab() { return SingleChildScrollView( padding: const EdgeInsets.all(24), child: Center( child: Container( constraints: const BoxConstraints(maxWidth: 600), child: Form( key: _formKey, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ if (_isLoading) const LinearProgressIndicator(), const SizedBox(height: 20), TextFormField( controller: _createLoginIdController, decoration: const InputDecoration( labelText: "Login ID (Required)", border: OutlineInputBorder(), helperText: "Unique identifier (Email or Phone)", ), validator: (value) => value == null || value.isEmpty ? 'Please enter Login ID' : null, ), const SizedBox(height: 16), TextFormField( controller: _createNameController, decoration: const InputDecoration( labelText: "Display Name", border: OutlineInputBorder(), prefixIcon: Icon(Icons.person), ), ), const SizedBox(height: 16), TextFormField( controller: _createEmailController, decoration: const InputDecoration( labelText: "Email", border: OutlineInputBorder(), prefixIcon: Icon(Icons.email), ), ), const SizedBox(height: 16), TextFormField( controller: _createPhoneController, decoration: const InputDecoration( labelText: "Phone Number", border: OutlineInputBorder(), prefixIcon: Icon(Icons.phone), helperText: "010-xxxx-xxxx", ), ), const SizedBox(height: 32), FilledButton( onPressed: _isLoading ? null : _createUserSubmit, style: FilledButton.styleFrom( padding: const EdgeInsets.symmetric(vertical: 16), ), child: const Text("Create User"), ), ], ), ), ), ), ); } }