import 'package:enter_farma_plus_mobile/src/app/widgets/app_panel.dart'; import 'package:enter_farma_plus_mobile/src/core/models/tenant_session.dart'; import 'package:enter_farma_plus_mobile/src/core/network/api_client.dart'; import 'package:enter_farma_plus_mobile/src/core/network/mobile_api_response.dart'; import 'package:enter_farma_plus_mobile/src/core/storage/tenant_session_store.dart'; import 'package:enter_farma_plus_mobile/src/core/theme/app_tokens.dart'; import 'package:enter_farma_plus_mobile/src/features/settings/presentation/printer_settings_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; const String _appVersion = '1.0.0'; class ProfileScreen extends ConsumerStatefulWidget { const ProfileScreen({super.key}); @override ConsumerState createState() => _ProfileScreenState(); } class _ProfileScreenState extends ConsumerState { late Future<_ProfileBundle> _future; @override void initState() { super.initState(); _future = _load(); } Future<_ProfileBundle> _load() async { try { final store = ref.read(tenantSessionStoreProvider); final session = await store.readSession(); if (session == null) { return const _ProfileBundle(session: null, bootstrap: null); } final bootstrap = await ref.read(apiClientProvider).bootstrap(); return _ProfileBundle(session: session, bootstrap: bootstrap); } catch (e) { return _ProfileBundle( session: null, bootstrap: MobileApiResponse.failure(message: e.toString()), ); } } Future _refresh() async { final future = _load(); setState(() { _future = future; }); await future; } String _initials(String name) { final parts = name.trim().split(RegExp(r'\s+')).where((s) => s.isNotEmpty).toList(); if (parts.isEmpty) return '?'; if (parts.length == 1) return parts.first.substring(0, 1).toUpperCase(); return (parts.first.substring(0, 1) + parts[1].substring(0, 1)) .toUpperCase(); } Future _showAbout(_ProfileBundle bundle) async { final tenant = Map.from( bundle.bootstrap?.data?['tenant'] as Map? ?? const {}); await showModalBottomSheet( context: context, builder: (ctx) => SafeArea( child: Padding( padding: const EdgeInsets.fromLTRB(20, 16, 20, 24), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ const Icon(Icons.info_outline_rounded, color: AppTokens.primary), const SizedBox(width: 10), Text('Acerca de', style: Theme.of(context).textTheme.titleMedium), ], ), const SizedBox(height: 18), const _Line( icon: Icons.smartphone_rounded, label: 'Aplicación', value: 'SysFarma'), const Divider(height: 22), const _Line( icon: Icons.tag_rounded, label: 'Versión', value: 'v$_appVersion'), const Divider(height: 22), _Line( icon: Icons.cloud_rounded, label: 'Tenant', value: tenant['host']?.toString() ?? '-'), const Divider(height: 22), _Line( icon: Icons.api_rounded, label: 'API', value: tenant['api_base_url']?.toString() ?? '-'), ], ), ), ), ); } Future _switchEstablishment( TenantSession currentSession, int establishmentId, ) async { final response = await ref.read(apiClientProvider).updateActiveEstablishment( establishmentId: establishmentId, ); if (!mounted) return; if (!response.success) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(response.message ?? 'No se pudo cambiar de sede.')), ); return; } final data = response.data ?? const {}; final user = Map.from(data['user'] as Map? ?? const {}); final activeEstablishmentId = int.tryParse( '${data['active_establishment_id'] ?? user['active_establishment_id'] ?? user['establishment_id'] ?? establishmentId}', ); await ref.read(tenantSessionStoreProvider).saveSession( TenantSession( baseUrl: currentSession.baseUrl, token: currentSession.token, companyName: currentSession.companyName, ruc: currentSession.ruc, userType: currentSession.userType, activeEstablishmentId: activeEstablishmentId, ), ); ref.invalidate(currentSessionProvider); await _refresh(); if (!mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar(content: Text('Sucursal actualizada.')), ); } @override Widget build(BuildContext context) { return SafeArea( child: RefreshIndicator( onRefresh: _refresh, child: FutureBuilder<_ProfileBundle>( future: _future, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.waiting) { return const Center(child: CircularProgressIndicator()); } final bundle = snapshot.data; if (bundle == null || bundle.session == null) { return ListView( padding: AppTokens.pagePadding, children: const [ AppPanel( title: 'Sin sesión', child: Text('Inicia sesión para ver tu perfil.'), ), ], ); } final data = bundle.bootstrap?.data ?? const {}; final company = Map.from(data['company'] as Map? ?? const {}); final session = Map.from(data['session'] as Map? ?? const {}); final user = Map.from(session['user'] as Map? ?? const {}); final establishment = Map.from( session['establishment'] as Map? ?? const {}); final allowedEstablishments = List>.from( session['allowed_establishments'] as List? ?? const [], ); final activeEstablishmentId = int.tryParse( '${session['active_establishment_id'] ?? user['active_establishment_id'] ?? user['establishment_id'] ?? ''}', ); final userName = user['name']?.toString() ?? 'Sin usuario'; final userEmail = user['email']?.toString() ?? '-'; final userType = user['type']?.toString() ?? ''; final initials = _initials(userName); final logoUrl = company['logo_url']?.toString(); final hasLogo = logoUrl != null && logoUrl.isNotEmpty && !logoUrl.contains('imagen-no-disponible'); return ListView( padding: AppTokens.pagePadding, children: [ // Hero compacto Container( padding: const EdgeInsets.all(20), decoration: BoxDecoration( gradient: const LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [AppTokens.primary, AppTokens.primaryDark], ), borderRadius: BorderRadius.circular(AppTokens.radiusMedium), ), child: Row( children: [ Container( width: 64, height: 64, clipBehavior: Clip.antiAlias, decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.18), shape: BoxShape.circle, ), child: hasLogo ? Image.network( logoUrl, fit: BoxFit.cover, errorBuilder: (_, __, ___) => Center( child: Text( initials, style: Theme.of(context) .textTheme .headlineSmall ?.copyWith( color: Colors.white, fontWeight: FontWeight.w700, ), ), ), ) : Center( child: Text( initials, style: Theme.of(context) .textTheme .headlineSmall ?.copyWith( color: Colors.white, fontWeight: FontWeight.w800, ), ), ), ), const SizedBox(width: 14), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( userName, maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context) .textTheme .titleLarge ?.copyWith( color: Colors.white, fontWeight: FontWeight.w800, ), ), const SizedBox(height: 2), Text( userEmail, maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context) .textTheme .bodySmall ?.copyWith( color: Colors.white.withValues(alpha: 0.85), ), ), if (userType.isNotEmpty) ...[ const SizedBox(height: 6), Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 3), decoration: BoxDecoration( color: Colors.white.withValues(alpha: 0.20), borderRadius: BorderRadius.circular(999), ), child: Text( userType.toUpperCase(), style: const TextStyle( color: Colors.white, fontSize: 11, fontWeight: FontWeight.w800, letterSpacing: 0.5, ), ), ), ], ], ), ), ], ), ), const SizedBox(height: 14), // Quick actions (configuración del usuario). // El cierre de sesión vive en el popup del avatar superior derecho. Row( children: [ Expanded( child: _QuickAction( icon: Icons.print_rounded, label: 'Impresora', color: AppTokens.primary, onTap: () => showPrinterSettings(context, ref), ), ), const SizedBox(width: 10), Expanded( child: _QuickAction( icon: Icons.lock_rounded, label: 'Contraseña', color: AppTokens.success, onTap: () => context.go('/profile/password'), ), ), const SizedBox(width: 10), Expanded( child: _QuickAction( icon: Icons.info_outline_rounded, label: 'Acerca de', color: AppTokens.secondary, onTap: () => _showAbout(bundle), ), ), ], ), const SizedBox(height: 14), // Negocio (Empresa + Sucursal en una sola card) AppPanel( title: 'Negocio', child: Column( children: [ const _SectionLabel('Empresa'), const SizedBox(height: 6), _Line( icon: Icons.business_rounded, label: 'Razón social', value: company['name']?.toString() ?? '-', ), const Divider(height: 20), _Line( icon: Icons.storefront_rounded, label: 'Nombre comercial', value: company['trade_name']?.toString() ?? '-', ), const Divider(height: 20), _Line( icon: Icons.badge_rounded, label: 'RUC', value: company['number']?.toString() ?? '-', ), const SizedBox(height: 18), const _SectionLabel('Sucursal'), const SizedBox(height: 6), if (allowedEstablishments.length > 1) ...[ DropdownButtonFormField( initialValue: activeEstablishmentId, isExpanded: true, decoration: const InputDecoration( labelText: 'Sucursal activa', ), items: allowedEstablishments.map((row) { final id = int.tryParse('${row['id']}'); if (id == null) return null; return DropdownMenuItem( value: id, child: Text( row['description']?.toString() ?? 'Sucursal', overflow: TextOverflow.ellipsis, ), ); }).whereType>().toList(), onChanged: (value) async { if (value == null || bundle.session == null) return; await _switchEstablishment(bundle.session!, value); }, ), const SizedBox(height: 12), ], _Line( icon: Icons.location_city_rounded, label: 'Nombre', value: establishment['description']?.toString() ?? '-', ), const Divider(height: 20), _Line( icon: Icons.location_on_outlined, label: 'Dirección', value: establishment['address']?.toString() ?? '-', ), const Divider(height: 20), _Line( icon: Icons.phone_outlined, label: 'Teléfono', value: establishment['telephone']?.toString() ?? '-', ), const Divider(height: 20), _Line( icon: Icons.mail_outline_rounded, label: 'Correo', value: establishment['email']?.toString() ?? '-', ), ], ), ), const SizedBox(height: 18), // Versión Center( child: Text( 'SysFarma · v$_appVersion', style: Theme.of(context).textTheme.labelSmall?.copyWith( color: AppTokens.secondary, ), ), ), const SizedBox(height: 24), ], ); }, ), ), ); } } class _QuickAction extends StatelessWidget { const _QuickAction({ required this.icon, required this.label, required this.color, required this.onTap, }); final IconData icon; final String label; final Color color; final VoidCallback onTap; @override Widget build(BuildContext context) { return Material( color: AppTokens.surface, borderRadius: BorderRadius.circular(AppTokens.radiusMedium), child: InkWell( borderRadius: BorderRadius.circular(AppTokens.radiusMedium), onTap: onTap, child: Container( padding: const EdgeInsets.symmetric(vertical: 14), decoration: BoxDecoration( borderRadius: BorderRadius.circular(AppTokens.radiusMedium), border: Border.all(color: AppTokens.border), ), child: Column( children: [ Container( width: 40, height: 40, decoration: BoxDecoration( color: color.withValues(alpha: 0.12), shape: BoxShape.circle, ), child: Icon(icon, color: color, size: 20), ), const SizedBox(height: 6), Text( label, style: Theme.of(context).textTheme.labelSmall?.copyWith( color: AppTokens.textPrimary, fontWeight: FontWeight.w600, ), ), ], ), ), ), ); } } class _SectionLabel extends StatelessWidget { const _SectionLabel(this.text); final String text; @override Widget build(BuildContext context) { return Align( alignment: Alignment.centerLeft, child: Text( text.toUpperCase(), style: Theme.of(context).textTheme.labelSmall?.copyWith( color: AppTokens.secondary, fontWeight: FontWeight.w800, letterSpacing: 0.6, ), ), ); } } class _ProfileBundle { const _ProfileBundle({required this.session, required this.bootstrap}); final TenantSession? session; final MobileApiResponse>? bootstrap; } class _Line extends StatelessWidget { const _Line({ required this.label, required this.value, this.icon, }); final String label; final String value; final IconData? icon; @override Widget build(BuildContext context) { return Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (icon != null) ...[ Icon(icon, size: 16, color: AppTokens.secondary), const SizedBox(width: 8), ], Expanded( child: Text( label, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: AppTokens.secondary, ), ), ), const SizedBox(width: 12), Expanded( child: Text( value, textAlign: TextAlign.end, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), ), ), ], ); } }