import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../core/providers/auth_provider.dart'; import '../../core/theme/app_theme.dart'; import '../../models/tenant.dart'; import 'tenant_team_repository.dart'; class TenantTeamScreen extends ConsumerStatefulWidget { const TenantTeamScreen({super.key}); @override ConsumerState createState() => _TenantTeamScreenState(); } class _TenantTeamScreenState extends ConsumerState { List _members = []; bool _loading = true; String? _error; @override void initState() { super.initState(); _load(); } Future _load() async { setState(() { _loading = true; _error = null; }); try { final tenantId = ref.read(authProvider).activeTenant!.tenant.id; final members = await TenantTeamRepository.instance.listMembers(tenantId); if (mounted) { setState(() { _members = members; _loading = false; }); } } catch (e) { if (mounted) { setState(() { _error = e.toString(); _loading = false; }); } } } bool get _canManage => ref.read(authProvider).activeTenant?.canManageUsers ?? false; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('Ekip'), actions: [ if (_canManage) TextButton.icon( onPressed: () => _showAddMemberSheet(context), icon: const Icon(Icons.person_add_outlined, size: 18), label: const Text('Üye Ekle'), ), ], ), body: _loading ? const Center(child: CircularProgressIndicator()) : _error != null ? _ErrorView(error: _error!, onRetry: _load) : RefreshIndicator( onRefresh: _load, child: ListView( padding: const EdgeInsets.all(16), children: [ _SectionHeader( title: 'Üyeler', count: _members.length, ), const SizedBox(height: 8), _MembersList( members: _members, canManage: _canManage, currentUserId: ref.read(authProvider).profile?.id ?? '', onRoleChange: _changeRole, onRemove: _removeMember, ), const SizedBox(height: 24), ], ), ), ); } Future _showAddMemberSheet(BuildContext context) async { final tenantId = ref.read(authProvider).activeTenant!.tenant.id; await showModalBottomSheet( context: context, isScrollControlled: true, backgroundColor: AppColors.background, shape: const RoundedRectangleBorder( borderRadius: BorderRadius.vertical(top: Radius.circular(20)), ), builder: (_) => _AddMemberSheet( onAdd: (firstName, lastName, email, password, role) async { await TenantTeamRepository.instance.addMember( tenantId: tenantId, email: email, password: password, firstName: firstName, lastName: lastName, role: role, ); await _load(); }, ), ); } Future _changeRole(TeamMember member, TenantRole newRole) async { try { await TenantTeamRepository.instance.changeMemberRole( member.memberId, newRole); await _load(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('Hata: $e'))); } } } Future _removeMember(TeamMember member) async { final name = member.user.displayName.isNotEmpty ? member.user.displayName : member.user.email; final confirmed = await showDialog( context: context, builder: (_) => AlertDialog( title: const Text('Üyeyi Çıkar'), content: Text('$name adlı üyeyi ekipten çıkarmak istiyor musunuz?'), actions: [ TextButton( onPressed: () => Navigator.pop(context, false), child: const Text('Vazgeç')), FilledButton( onPressed: () => Navigator.pop(context, true), style: FilledButton.styleFrom(backgroundColor: AppColors.cancelled), child: const Text('Çıkar'), ), ], ), ); if (confirmed != true) return; try { await TenantTeamRepository.instance.removeMember(member.memberId); await _load(); } catch (e) { if (mounted) { ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text('Hata: $e'))); } } } } // ── Section header ───────────────────────────────────────────────────────── class _SectionHeader extends StatelessWidget { const _SectionHeader({required this.title, required this.count}); final String title; final int count; @override Widget build(BuildContext context) { return Row( children: [ Text( title, style: const TextStyle( fontSize: 13, fontWeight: FontWeight.w700, color: AppColors.textSecondary, letterSpacing: 0.5, ), ), const SizedBox(width: 8), Container( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), decoration: BoxDecoration( color: AppColors.inProgressBg, borderRadius: BorderRadius.circular(20), ), child: Text( '$count', style: const TextStyle( fontSize: 12, fontWeight: FontWeight.w700, color: AppColors.inProgress, ), ), ), ], ); } } // ── Members list ─────────────────────────────────────────────────────────── class _MembersList extends StatelessWidget { const _MembersList({ required this.members, required this.canManage, required this.currentUserId, required this.onRoleChange, required this.onRemove, }); final List members; final bool canManage; final String currentUserId; final Future Function(TeamMember, TenantRole) onRoleChange; final Future Function(TeamMember) onRemove; @override Widget build(BuildContext context) { if (members.isEmpty) { return const _EmptyCard(message: 'Henüz üye yok.'); } return Container( decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border), boxShadow: [ BoxShadow( color: Colors.black.withValues(alpha: 0.05), blurRadius: 12, offset: const Offset(0, 4)) ], ), child: Column( children: members.asMap().entries.map((entry) { final i = entry.key; final m = entry.value; final isLast = i == members.length - 1; return _MemberTile( member: m, isSelf: m.user.id == currentUserId, canManage: canManage && m.role != TenantRole.owner, showDivider: !isLast, onRoleChange: (role) => onRoleChange(m, role), onRemove: () => onRemove(m), ); }).toList(), ), ); } } class _MemberTile extends StatelessWidget { const _MemberTile({ required this.member, required this.isSelf, required this.canManage, required this.showDivider, required this.onRoleChange, required this.onRemove, }); final TeamMember member; final bool isSelf; final bool canManage; final bool showDivider; final void Function(TenantRole) onRoleChange; final VoidCallback onRemove; @override Widget build(BuildContext context) { final name = member.user.displayName.isNotEmpty ? member.user.displayName : member.user.email; final initials = name.trim().isNotEmpty ? name.trim()[0].toUpperCase() : '?'; return Column( mainAxisSize: MainAxisSize.min, children: [ Padding( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), child: Row( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: AppColors.inProgressBg, borderRadius: BorderRadius.circular(20), ), child: Center( child: Text( initials, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.w700, color: AppColors.inProgress, ), ), ), ), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text( name, style: const TextStyle( fontWeight: FontWeight.w600, color: AppColors.textPrimary, ), ), if (isSelf) ...[ const SizedBox(width: 6), Container( padding: const EdgeInsets.symmetric( horizontal: 6, vertical: 1), decoration: BoxDecoration( color: AppColors.successBg, borderRadius: BorderRadius.circular(6), ), child: const Text( 'Sen', style: TextStyle( fontSize: 10, fontWeight: FontWeight.w700, color: AppColors.success, ), ), ), ], ], ), Text( member.user.email, style: const TextStyle( fontSize: 12, color: AppColors.textSecondary, ), ), ], ), ), if (canManage) ...[ _RoleChip( role: member.role, onChanged: onRoleChange, ), const SizedBox(width: 4), IconButton( onPressed: onRemove, icon: const Icon(Icons.remove_circle_outline, color: AppColors.cancelled, size: 20), tooltip: 'Çıkar', constraints: const BoxConstraints(), padding: const EdgeInsets.all(6), ), ] else _RoleBadge(role: member.role), ], ), ), if (showDivider) const Divider(height: 1, indent: 68, color: AppColors.border), ], ); } } class _RoleChip extends StatelessWidget { const _RoleChip({required this.role, required this.onChanged}); final TenantRole role; final void Function(TenantRole) onChanged; static const _selectableRoles = [ TenantRole.admin, TenantRole.technician, TenantRole.delivery, TenantRole.finance, TenantRole.doctor, TenantRole.member, ]; @override Widget build(BuildContext context) { return PopupMenuButton( initialValue: role, onSelected: onChanged, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), itemBuilder: (_) => _selectableRoles .map((r) => PopupMenuItem( value: r, child: Text(r.label), )) .toList(), child: Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( color: _roleBg(role), borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ Text( role.label, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: _roleColor(role), ), ), const SizedBox(width: 4), Icon(Icons.arrow_drop_down, size: 16, color: _roleColor(role)), ], ), ), ); } } class _RoleBadge extends StatelessWidget { const _RoleBadge({required this.role}); final TenantRole role; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 5), decoration: BoxDecoration( color: _roleBg(role), borderRadius: BorderRadius.circular(8), ), child: Text( role.label, style: TextStyle( fontSize: 12, fontWeight: FontWeight.w600, color: _roleColor(role), ), ), ); } } Color _roleBg(TenantRole r) => switch (r) { TenantRole.owner => AppColors.inProgressBg, TenantRole.admin => AppColors.inProgressBg, TenantRole.doctor => AppColors.successBg, _ => AppColors.surface, }; Color _roleColor(TenantRole r) => switch (r) { TenantRole.owner => AppColors.inProgress, TenantRole.admin => AppColors.inProgress, TenantRole.doctor => AppColors.success, _ => AppColors.textSecondary, }; // ── Add member sheet ──────────────────────────────────────────────────────── class _AddMemberSheet extends StatefulWidget { const _AddMemberSheet({required this.onAdd}); final Future Function( String firstName, String lastName, String email, String password, TenantRole role, ) onAdd; @override State<_AddMemberSheet> createState() => _AddMemberSheetState(); } class _AddMemberSheetState extends State<_AddMemberSheet> { final _formKey = GlobalKey(); final _firstNameController = TextEditingController(); final _lastNameController = TextEditingController(); final _emailController = TextEditingController(); final _passwordController = TextEditingController(); TenantRole _selectedRole = TenantRole.member; bool _saving = false; bool _obscurePassword = true; static const _selectableRoles = [ TenantRole.admin, TenantRole.technician, TenantRole.delivery, TenantRole.finance, TenantRole.doctor, TenantRole.member, ]; @override void dispose() { _firstNameController.dispose(); _lastNameController.dispose(); _emailController.dispose(); _passwordController.dispose(); super.dispose(); } Future _submit() async { if (!_formKey.currentState!.validate()) return; setState(() => _saving = true); try { await widget.onAdd( _firstNameController.text.trim(), _lastNameController.text.trim(), _emailController.text.trim(), _passwordController.text, _selectedRole, ); if (mounted) Navigator.pop(context); } catch (e) { if (mounted) { final msg = _friendlyError(e); ScaffoldMessenger.of(context) .showSnackBar(SnackBar(content: Text(msg))); } } finally { if (mounted) setState(() => _saving = false); } } static String _friendlyError(Object e) { final s = e.toString(); if (s.contains('email') && s.contains('unique')) { return 'Bu e-posta adresi zaten kayıtlı.'; } final msgMatch = RegExp(r'message: ([^,}]+)').firstMatch(s); if (msgMatch != null) return msgMatch.group(1)!.trim(); if (s.length > 120) return 'Sunucu hatası'; return s; } @override Widget build(BuildContext context) { final bottom = MediaQuery.of(context).viewInsets.bottom; return Padding( padding: EdgeInsets.fromLTRB(24, 24, 24, 24 + bottom), child: Form( key: _formKey, child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Text( 'Üye Ekle', style: TextStyle( fontSize: 20, fontWeight: FontWeight.w700, color: AppColors.textPrimary, ), ), const Spacer(), IconButton( onPressed: () => Navigator.pop(context), icon: const Icon(Icons.close), ), ], ), const SizedBox(height: 20), Row( children: [ Expanded( child: TextFormField( controller: _firstNameController, textCapitalization: TextCapitalization.words, textInputAction: TextInputAction.next, decoration: const InputDecoration( labelText: 'İsim', prefixIcon: Icon(Icons.person_outline), ), validator: (val) { if (val == null || val.trim().isEmpty) return 'Zorunlu'; return null; }, ), ), const SizedBox(width: 12), Expanded( child: TextFormField( controller: _lastNameController, textCapitalization: TextCapitalization.words, textInputAction: TextInputAction.next, decoration: const InputDecoration( labelText: 'Soyisim', ), validator: (val) { if (val == null || val.trim().isEmpty) return 'Zorunlu'; return null; }, ), ), ], ), const SizedBox(height: 16), TextFormField( controller: _emailController, keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, autocorrect: false, decoration: const InputDecoration( labelText: 'E-posta', hintText: 'ornek@email.com', prefixIcon: Icon(Icons.email_outlined), ), validator: (val) { if (val == null || val.trim().isEmpty) return 'E-posta zorunludur'; if (!val.contains('@')) return 'Geçerli bir e-posta girin'; return null; }, ), const SizedBox(height: 16), TextFormField( controller: _passwordController, obscureText: _obscurePassword, textInputAction: TextInputAction.done, decoration: InputDecoration( labelText: 'Şifre', prefixIcon: const Icon(Icons.lock_outline), suffixIcon: IconButton( icon: Icon(_obscurePassword ? Icons.visibility_outlined : Icons.visibility_off_outlined), onPressed: () => setState(() => _obscurePassword = !_obscurePassword), ), ), validator: (val) { if (val == null || val.isEmpty) return 'Şifre zorunludur'; if (val.length < 8) return 'En az 8 karakter olmalı'; return null; }, ), const SizedBox(height: 16), DropdownButtonFormField( value: _selectedRole, decoration: const InputDecoration( labelText: 'Rol', prefixIcon: Icon(Icons.badge_outlined), ), items: _selectableRoles .map((r) => DropdownMenuItem( value: r, child: Text(r.label), )) .toList(), onChanged: (v) { if (v != null) setState(() => _selectedRole = v); }, ), const SizedBox(height: 24), SizedBox( width: double.infinity, child: FilledButton.icon( onPressed: _saving ? null : _submit, icon: _saving ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white), ) : const Icon(Icons.person_add_outlined, size: 18), label: const Text('Üye Ekle'), ), ), ], ), ), ); } } // ── Helpers ──────────────────────────────────────────────────────────────── class _EmptyCard extends StatelessWidget { const _EmptyCard({required this.message}); final String message; @override Widget build(BuildContext context) { return Container( padding: const EdgeInsets.all(24), decoration: BoxDecoration( color: AppColors.surface, borderRadius: BorderRadius.circular(16), border: Border.all(color: AppColors.border), ), child: Center( child: Text( message, style: const TextStyle(color: AppColors.textSecondary), ), ), ); } } class _ErrorView extends StatelessWidget { const _ErrorView({required this.error, required this.onRetry}); final String error; final VoidCallback onRetry; @override Widget build(BuildContext context) { return Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ const Icon(Icons.error_outline, color: AppColors.cancelled, size: 40), const SizedBox(height: 12), Text(error, style: const TextStyle(color: AppColors.textSecondary), textAlign: TextAlign.center), const SizedBox(height: 12), TextButton(onPressed: onRetry, child: const Text('Tekrar Dene')), ], ), ); } }