import 'dart:async'; import 'package:flutter/material.dart'; import 'package:shadcn_ui/shadcn_ui.dart'; import 'package:superport/models/user_model.dart'; import 'package:superport/screens/common/theme_shadcn.dart'; import 'package:superport/screens/common/components/shadcn_components.dart'; import 'package:superport/screens/common/layouts/base_list_screen.dart'; import 'package:superport/screens/common/widgets/standard_data_table.dart'; import 'package:superport/screens/common/widgets/standard_action_bar.dart'; import 'package:superport/screens/common/widgets/pagination.dart'; import 'package:superport/screens/user/controllers/user_list_controller.dart'; import 'package:superport/utils/constants.dart'; /// shadcn/ui 스타일로 재설계된 사용자 관리 화면 class UserList extends StatefulWidget { const UserList({super.key}); @override State createState() => _UserListState(); } class _UserListState extends State { late UserListController _controller; final TextEditingController _searchController = TextEditingController(); @override void initState() { super.initState(); _controller = UserListController(); WidgetsBinding.instance.addPostFrameCallback((_) { _controller.initialize(pageSize: 10); }); _searchController.addListener(() { _onSearchChanged(_searchController.text); }); } @override void dispose() { _controller.dispose(); _searchController.dispose(); super.dispose(); } Timer? _debounce; void _onSearchChanged(String query) { if (_debounce?.isActive ?? false) _debounce?.cancel(); _debounce = Timer(const Duration(milliseconds: 500), () { _controller.setSearchQuery(query); }); } /// 사용자 권한 표시 배지 Widget _buildUserRoleBadge(UserRole role) { final roleName = role.displayName; ShadcnBadgeVariant variant; switch (role) { case UserRole.admin: variant = ShadcnBadgeVariant.destructive; break; case UserRole.manager: variant = ShadcnBadgeVariant.primary; break; case UserRole.staff: variant = ShadcnBadgeVariant.secondary; break; } return ShadcnBadge( text: roleName, variant: variant, size: ShadcnBadgeSize.small, ); } /// 사용자 추가 폼으로 이동 void _navigateToAdd() async { final result = await Navigator.pushNamed(context, Routes.userAdd); if (result == true && mounted) { _controller.loadUsers(refresh: true); } } /// 사용자 수정 폼으로 이동 void _navigateToEdit(int userId) async { final result = await Navigator.pushNamed( context, Routes.userEdit, arguments: userId, ); if (result == true && mounted) { _controller.loadUsers(refresh: true); } } /// 사용자 삭제 다이얼로그 void _showDeleteDialog(int userId, String userName) { showShadDialog( context: context, builder: (context) => ShadDialog( title: const Text('사용자 삭제'), description: Text('"$userName" 사용자를 정말로 삭제하시겠습니까?'), actions: [ ShadButton.outline( child: const Text('취소'), onPressed: () => Navigator.of(context).pop(), ), ShadButton.destructive( child: const Text('삭제'), onPressed: () async { Navigator.of(context).pop(); await _controller.deleteUser(userId); ShadToaster.of(context).show( ShadToast( title: const Text('삭제 완료'), description: const Text('사용자가 삭제되었습니다'), ), ); }, ), ], ), ); } /// 상태 변경 확인 다이얼로그 void _showStatusChangeDialog(User user) { final newStatus = !user.isActive; final statusText = newStatus ? '활성화' : '비활성화'; showShadDialog( context: context, builder: (context) => ShadDialog( title: const Text('사용자 상태 변경'), description: Text('"${user.name}" 사용자를 $statusText 하시겠습니까?'), actions: [ ShadButton.outline( child: const Text('취소'), onPressed: () => Navigator.of(context).pop(), ), ShadButton( child: Text(statusText), onPressed: () async { Navigator.of(context).pop(); await _controller.changeUserStatus(user, !user.isActive); }, ), ], ), ); } @override Widget build(BuildContext context) { return Scaffold( backgroundColor: ShadcnTheme.background, appBar: AppBar( title: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( '사용자 관리', style: ShadcnTheme.headingH4, ), Text( '시스템 사용자를 관리합니다', style: ShadcnTheme.bodySmall, ), ], ), backgroundColor: ShadcnTheme.background, elevation: 0, ), body: ListenableBuilder( listenable: _controller, builder: (context, child) { return BaseListScreen( headerSection: _buildStatisticsCards(), searchBar: _buildSearchBar(), actionBar: _buildActionBar(), dataTable: _buildDataTable(), pagination: _buildPagination(), isLoading: _controller.isLoading && _controller.users.isEmpty, error: _controller.error, onRefresh: () => _controller.loadUsers(refresh: true), ); }, ), ); } Widget _buildStatisticsCards() { if (_controller.isLoading) return const SizedBox(); return Row( children: [ _buildStatCard( '전체 사용자', _controller.total.toString(), Icons.people, ShadcnTheme.primary, ), const SizedBox(width: ShadcnTheme.spacing4), _buildStatCard( '활성 사용자', _controller.users.where((u) => u.isActive).length.toString(), Icons.check_circle, ShadcnTheme.success, ), const SizedBox(width: ShadcnTheme.spacing4), _buildStatCard( '비활성 사용자', _controller.users.where((u) => !u.isActive).length.toString(), Icons.person_off, ShadcnTheme.mutedForeground, ), ], ); } Widget _buildSearchBar() { return Row( children: [ Expanded( child: ShadInputFormField( controller: _searchController, placeholder: const Text('이름, 이메일로 검색...'), ), ), const SizedBox(width: ShadcnTheme.spacing4), ShadButton.outline( onPressed: () => _controller.clearFilters(), child: const Text('초기화'), ), ], ); } Widget _buildActionBar() { return StandardActionBar( totalCount: _controller.totalCount, leftActions: const [ Text('사용자 목록', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), ], rightActions: [ ShadButton( onPressed: _navigateToAdd, child: const Row( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.person_add, size: 16), SizedBox(width: ShadcnTheme.spacing1), Text('사용자 추가'), ], ), ), ], ); } Widget _buildDataTable() { if (_controller.users.isEmpty && !_controller.isLoading) { return StandardDataTable( columns: _getColumns(), rows: const [], emptyMessage: '등록된 사용자가 없습니다', emptyIcon: Icons.people_outlined, ); } return StandardDataTable( columns: _getColumns(), rows: _buildRows(), fixedHeader: true, maxHeight: 600, ); } List _getColumns() { return [ StandardDataColumn(label: 'No.', width: 60), StandardDataColumn(label: '이름', flex: 1), StandardDataColumn(label: '이메일', flex: 2), StandardDataColumn(label: '회사', flex: 1), StandardDataColumn(label: '권한', width: 80), StandardDataColumn(label: '상태', width: 80), StandardDataColumn(label: '작업', width: 120), ]; } List _buildRows() { return _controller.users.asMap().entries.map((entry) { final index = entry.key; final user = entry.value; final rowNumber = (_controller.currentPage - 1) * _controller.pageSize + index + 1; return StandardDataRow( index: index, cells: [ Text( rowNumber.toString(), style: ShadcnTheme.bodyMedium, ), Text( user.name, style: ShadcnTheme.bodyMedium.copyWith( fontWeight: FontWeight.w500, ), ), Text( user.email, style: ShadcnTheme.bodyMedium, ), Text( '-', // Company name not available in current model style: ShadcnTheme.bodySmall, ), _buildUserRoleBadge(user.role), _buildStatusChip(user.isActive), Row( mainAxisSize: MainAxisSize.min, children: [ ShadButton.ghost( onPressed: user.id != null ? () => _navigateToEdit(user.id!) : null, child: const Icon(Icons.edit, size: 16), ), const SizedBox(width: ShadcnTheme.spacing1), ShadButton.ghost( onPressed: () => _showStatusChangeDialog(user), child: Icon( user.isActive ? Icons.person_off : Icons.person, size: 16, ), ), const SizedBox(width: ShadcnTheme.spacing1), ShadButton.ghost( onPressed: user.id != null ? () => _showDeleteDialog(user.id!, user.name) : null, child: const Icon(Icons.delete, size: 16), ), ], ), ], ); }).toList(); } Widget _buildPagination() { if (_controller.totalPages <= 1) return const SizedBox(); return Pagination( currentPage: _controller.currentPage, totalCount: _controller.total, pageSize: _controller.pageSize, onPageChanged: (page) => _controller.goToPage(page), ); } Widget _buildStatCard( String title, String value, IconData icon, Color color, ) { return Expanded( child: ShadCard( child: Padding( padding: const EdgeInsets.all(ShadcnTheme.spacing4), child: Row( children: [ Container( padding: const EdgeInsets.all(ShadcnTheme.spacing3), decoration: BoxDecoration( color: color.withValues(alpha: 0.1), borderRadius: BorderRadius.circular(ShadcnTheme.radiusLg), ), child: Icon( icon, color: color, size: 20, ), ), const SizedBox(width: ShadcnTheme.spacing3), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( title, style: ShadcnTheme.bodySmall, ), Text( value, style: ShadcnTheme.headingH6.copyWith( color: color, ), ), ], ), ), ], ), ), ), ); } Widget _buildStatusChip(bool isActive) { if (isActive) { return ShadBadge.secondary( child: const Text('활성'), ); } else { return ShadBadge.destructive( child: const Text('비활성'), ); } } // StandardDataRow 임시 정의 } /// 표준 데이터 행 위젯 (임시) class StandardDataRow extends StatelessWidget { final int index; final List cells; final VoidCallback? onTap; final bool selected; const StandardDataRow({ super.key, required this.index, required this.cells, this.onTap, this.selected = false, }); @override Widget build(BuildContext context) { return InkWell( onTap: onTap, child: Container( height: 56, padding: const EdgeInsets.symmetric( horizontal: ShadcnTheme.spacing4, vertical: ShadcnTheme.spacing3, ), decoration: BoxDecoration( color: selected ? ShadcnTheme.primaryLight.withValues(alpha: 0.1) : (index.isEven ? ShadcnTheme.muted.withValues(alpha: 0.3) : null), border: const Border( bottom: BorderSide(color: ShadcnTheme.border, width: 1), ), ), child: Row( children: _buildCellWidgets(), ), ), ); } List _buildCellWidgets() { return cells.asMap().entries.map((entry) { final index = entry.key; final cell = entry.value; // 마지막 셀이 아니면 오른쪽에 간격 추가 if (index < cells.length - 1) { return Expanded( child: Padding( padding: const EdgeInsets.only(right: ShadcnTheme.spacing2), child: cell, ), ); } else { return cell; } }).toList(); } }