import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../models/office.dart'; import '../../providers/auth_provider.dart' show sessionProvider; import '../../providers/profile_provider.dart'; import '../../providers/tickets_provider.dart'; import '../../providers/user_offices_provider.dart'; import '../../widgets/multi_select_picker.dart'; import '../../widgets/responsive_body.dart'; class ProfileScreen extends ConsumerStatefulWidget { const ProfileScreen({super.key}); @override ConsumerState createState() => _ProfileScreenState(); } class _ProfileScreenState extends ConsumerState { final _detailsKey = GlobalKey(); final _passwordKey = GlobalKey(); final _fullNameController = TextEditingController(); final _newPasswordController = TextEditingController(); final _confirmPasswordController = TextEditingController(); List _selectedOfficeIds = []; bool _savingDetails = false; bool _changingPassword = false; bool _savingOffices = false; @override void dispose() { _fullNameController.dispose(); _newPasswordController.dispose(); _confirmPasswordController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final profileAsync = ref.watch(currentProfileProvider); final officesAsync = ref.watch(officesProvider); final userOfficesAsync = ref.watch(userOfficesProvider); final userId = ref.watch(currentUserIdProvider); final session = ref.watch(sessionProvider); // Populate controllers from profile stream (if not editing) profileAsync.whenData((p) { final name = p?.fullName ?? ''; if (_fullNameController.text != name) { _fullNameController.text = name; } }); // Populate selected offices from userOfficesProvider final assignedOfficeIds = userOfficesAsync.valueOrNull ?.where((u) => u.userId == userId) .map((u) => u.officeId) .toList() ?? []; if (_selectedOfficeIds.isEmpty) { _selectedOfficeIds = List.from(assignedOfficeIds); } return ResponsiveBody( child: SingleChildScrollView( padding: const EdgeInsets.only(top: 16, bottom: 32), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('My Profile', style: Theme.of(context).textTheme.titleLarge), const SizedBox(height: 12), // Details Card Card( child: Padding( padding: const EdgeInsets.all(16), child: Form( key: _detailsKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Account details', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 12), // Email (read-only) TextFormField( initialValue: session?.user.email ?? '', decoration: const InputDecoration(labelText: 'Email'), readOnly: true, ), const SizedBox(height: 12), TextFormField( controller: _fullNameController, decoration: const InputDecoration( labelText: 'Full name', ), validator: (v) => (v ?? '').trim().isEmpty ? 'Full name is required' : null, ), const SizedBox(height: 12), Row( children: [ ElevatedButton( onPressed: _savingDetails ? null : _onSaveDetails, child: Text( _savingDetails ? 'Saving...' : 'Save details', ), ), ], ), ], ), ), ), ), const SizedBox(height: 12), // Change password Card Card( child: Padding( padding: const EdgeInsets.all(16), child: Form( key: _passwordKey, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Password', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 8), const Text( 'Set or change your password. OAuth users (Google/Meta) can set a password here.', ), const SizedBox(height: 12), TextFormField( controller: _newPasswordController, decoration: const InputDecoration( labelText: 'New password', ), obscureText: true, validator: (v) { if (v == null || v.isEmpty) { return null; // allow empty to skip } if ((v).length < 8) { return 'Password must be at least 8 characters'; } return null; }, ), const SizedBox(height: 8), TextFormField( controller: _confirmPasswordController, decoration: const InputDecoration( labelText: 'Confirm password', ), obscureText: true, validator: (v) { final pw = _newPasswordController.text; if (pw.isEmpty) { return null; } if (v != pw) { return 'Passwords do not match'; } return null; }, ), const SizedBox(height: 12), Row( children: [ ElevatedButton( onPressed: _changingPassword ? null : _onChangePassword, child: Text( _changingPassword ? 'Updating...' : 'Change password', ), ), ], ), ], ), ), ), ), const SizedBox(height: 12), // Offices Card Card( child: Padding( padding: const EdgeInsets.all(16), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'Offices', style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 12), officesAsync.when( data: (offices) { return Column( children: [ MultiSelectPicker( label: 'Offices', items: offices, selectedIds: _selectedOfficeIds, getId: (o) => o.id, getLabel: (o) => o.name, onChanged: (ids) => setState(() => _selectedOfficeIds = ids), ), const SizedBox(height: 12), Row( children: [ ElevatedButton( onPressed: _savingOffices ? null : _onSaveOffices, child: Text( _savingOffices ? 'Saving...' : 'Save offices', ), ), ], ), ], ); }, loading: () => const Center(child: CircularProgressIndicator()), error: (e, _) => Text('Failed to load offices: $e'), ), ], ), ), ), ], ), ), ); } Future _onSaveDetails() async { if (!_detailsKey.currentState!.validate()) return; final id = ref.read(currentUserIdProvider); if (id == null) return; setState(() => _savingDetails = true); try { await ref .read(profileControllerProvider) .updateFullName( userId: id, fullName: _fullNameController.text.trim(), ); if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Profile updated.'))); // Refresh providers so other UI picks up the change immediately ref.invalidate(currentProfileProvider); ref.invalidate(profilesProvider); } catch (e) { if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Update failed: $e'))); } finally { if (mounted) setState(() => _savingDetails = false); } } Future _onChangePassword() async { if (!_passwordKey.currentState!.validate()) return; final pw = _newPasswordController.text; if (pw.isEmpty) { // nothing to do return; } setState(() => _changingPassword = true); try { await ref.read(profileControllerProvider).updatePassword(pw); _newPasswordController.clear(); _confirmPasswordController.clear(); if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Password updated.'))); } catch (e) { if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Password update failed: $e'))); } finally { if (mounted) setState(() => _changingPassword = false); } } Future _onSaveOffices() async { final id = ref.read(currentUserIdProvider); if (id == null) return; setState(() => _savingOffices = true); try { final assignments = ref.read(userOfficesProvider).valueOrNull ?? []; final assigned = assignments .where((a) => a.userId == id) .map((a) => a.officeId) .toSet(); final selected = _selectedOfficeIds.toSet(); final toAdd = selected.difference(assigned); final toRemove = assigned.difference(selected); final ctrl = ref.read(userOfficesControllerProvider); for (final officeId in toAdd) { await ctrl.assignUserOffice(userId: id, officeId: officeId); } for (final officeId in toRemove) { await ctrl.removeUserOffice(userId: id, officeId: officeId); } if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(const SnackBar(content: Text('Offices updated.'))); ref.invalidate(userOfficesProvider); } catch (e) { if (!mounted) return; ScaffoldMessenger.of( context, ).showSnackBar(SnackBar(content: Text('Failed to save offices: $e'))); } finally { if (mounted) setState(() => _savingOffices = false); } } }