From 31a56d37f85f11d8cf67a9fcda8ee91e5cbd42f0 Mon Sep 17 00:00:00 2001 From: mark-cachy Date: Mon, 20 Apr 2026 19:30:36 +0200 Subject: [PATCH] fixed MasterDataPage buttons --- lib/core/blocs/session/session_bloc.dart | 18 ++--- lib/core/widgets/flux_text_field.dart | 57 ++++++++++++---- lib/features/auth/ui/auth_screen.dart | 1 + .../master_data/master_data_hub_content.dart | 68 ++++++++++--------- 4 files changed, 88 insertions(+), 56 deletions(-) diff --git a/lib/core/blocs/session/session_bloc.dart b/lib/core/blocs/session/session_bloc.dart index 3ea9e2e..1583fce 100644 --- a/lib/core/blocs/session/session_bloc.dart +++ b/lib/core/blocs/session/session_bloc.dart @@ -2,6 +2,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flux/core/enums/enums.dart'; import 'package:flux/features/company/models/company_model.dart'; +import 'package:flux/features/master_data/store/data/store_repository.dart'; import 'package:flux/features/master_data/store/models/store_model.dart'; import 'package:get_it/get_it.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -13,6 +14,7 @@ part 'session_state.dart'; class SessionBloc extends Bloc { final SupabaseClient _supabase = GetIt.I.get(); + final StoreRepository _storeRepository = GetIt.I.get(); StreamSubscription? _authSubscription; SessionBloc() : super(const SessionState(status: SessionStatus.unknown)) { @@ -56,10 +58,7 @@ class SessionBloc extends Bloc { CompanyModel company = CompanyModel.fromJson(companyJson); // 2. Controlla i negozi - final stores = await _supabase - .from('store') - .select() - .eq('company_id', companyJson['id']); + final stores = await _storeRepository.fetchAllCompanyStores(company.id); if (stores.isEmpty) { emit( @@ -71,27 +70,24 @@ class SessionBloc extends Bloc { ); return; } - final availableStores = stores.map((s) => StoreModel.fromMap(s)).toList(); // 3. Tutto ok, gestiamo le SharedPreferences per il negozio final prefs = GetIt.I.get(); String? lastStoreId = prefs.getString(PrefKeys.lastStore.value); // Se non c'è nelle SharedPreferences, prendi il primo della lista - if (lastStoreId == null || !stores.any((s) => s['id'] == lastStoreId)) { - lastStoreId = stores.first['id']; + if (lastStoreId == null || !stores.any((s) => s.id == lastStoreId)) { + lastStoreId = stores.first.id; await prefs.setString('last_store_id', lastStoreId!); } - final selectedStore = StoreModel.fromMap( - stores.firstWhere((s) => s['id'] == lastStoreId), - ); + final selectedStore = stores.firstWhere((s) => s.id == lastStoreId); emit( SessionState( status: SessionStatus.ready, userId: event.userId, company: company, selectedStore: selectedStore, - availableStores: availableStores, + availableStores: stores, ), ); }); diff --git a/lib/core/widgets/flux_text_field.dart b/lib/core/widgets/flux_text_field.dart index 8a442bb..a60afe5 100644 --- a/lib/core/widgets/flux_text_field.dart +++ b/lib/core/widgets/flux_text_field.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flux/core/theme/theme.dart'; -class FluxTextField extends StatelessWidget { +class FluxTextField extends StatefulWidget { final String label; final IconData icon; final bool isPassword; @@ -30,20 +30,38 @@ class FluxTextField extends StatelessWidget { this.maxLength, }); + @override + State createState() => _FluxTextFieldState(); +} + +class _FluxTextFieldState extends State { + late bool _obscureText; + + @override + void initState() { + super.initState(); + _obscureText = widget.isPassword; + } + @override Widget build(BuildContext context) { return TextField( - controller: controller, - obscureText: isPassword, - keyboardType: keyboardType, - autofocus: autoFocus, - minLines: minLines, + controller: widget.controller, + obscureText: _obscureText, + enableSuggestions: !widget.isPassword, + autocorrect: !widget.isPassword, + keyboardType: widget.keyboardType, + autofocus: widget.autoFocus, + minLines: widget.minLines, // Se minLines è impostato, maxLines deve essere almeno uguale o null (espandibile) - maxLines: minLines != null ? null : maxLines, + maxLines: widget.minLines != null ? null : widget.maxLines, style: TextStyle(color: context.primaryText), decoration: InputDecoration( - prefixIcon: Icon(icon, color: context.accent.withValues(alpha: 0.6)), - labelText: label, + prefixIcon: Icon( + widget.icon, + color: context.accent.withValues(alpha: 0.6), + ), + labelText: widget.label, labelStyle: TextStyle(color: context.secondaryText, fontSize: 14), filled: true, fillColor: context.surface.withValues(alpha: 0.5), @@ -61,10 +79,25 @@ class FluxTextField extends StatelessWidget { horizontal: 16, vertical: 16, ), + suffixIcon: widget.isPassword + ? IconButton( + icon: Icon( + // Cambiamo icona in base allo stato + _obscureText ? Icons.visibility_off : Icons.visibility, + color: Colors.grey, + ), + onPressed: () { + // Quando l'utente clicca, invertiamo lo stato e ridisegniamo + setState(() { + _obscureText = !_obscureText; + }); + }, + ) + : null, // Se non è una password, niente icona ), - onSubmitted: onSubmitted, - onChanged: onChanged, - maxLength: maxLength, + onSubmitted: widget.onSubmitted, + onChanged: widget.onChanged, + maxLength: widget.maxLength, ); } } diff --git a/lib/features/auth/ui/auth_screen.dart b/lib/features/auth/ui/auth_screen.dart index 5542665..96a37e3 100644 --- a/lib/features/auth/ui/auth_screen.dart +++ b/lib/features/auth/ui/auth_screen.dart @@ -15,6 +15,7 @@ class AuthScreen extends StatefulWidget { class _AuthScreenState extends State { final _emailController = TextEditingController(); final _passwordController = TextEditingController(); + final _isPassword = true; @override void dispose() { diff --git a/lib/features/master_data/master_data_hub_content.dart b/lib/features/master_data/master_data_hub_content.dart index 03082d8..57ee187 100644 --- a/lib/features/master_data/master_data_hub_content.dart +++ b/lib/features/master_data/master_data_hub_content.dart @@ -34,10 +34,15 @@ class MasterDataHubContent extends StatelessWidget { const SizedBox(height: 32), Expanded( - child: GridView.count( - crossAxisCount: MediaQuery.of(context).size.width > 600 ? 3 : 2, - mainAxisSpacing: 16, - crossAxisSpacing: 16, + child: GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: MediaQuery.of(context).size.width > 600 ? 3 : 2, + mainAxisSpacing: 14, + crossAxisSpacing: 14, + // LA MAGIA: Fissiamo l'altezza della card a 200 pixel + // indipendentemente da quanto sia stretta la colonna! + mainAxisExtent: 200, + ), children: [ _buildHubCard( context, @@ -45,9 +50,7 @@ class MasterDataHubContent extends StatelessWidget { subtitle: 'Anagrafica di Marche e Modelli', icon: Icons.inventory_2_outlined, color: Colors.blue, - onTap: () => onOpenPage( - const ProductsScreen(), - ), // Apre ProductsScreen, // Indice per ProductsScreen + onTap: () => onOpenPage(const ProductsScreen()), ), _buildHubCard( context, @@ -55,9 +58,7 @@ class MasterDataHubContent extends StatelessWidget { subtitle: 'Anagrafica dei clienti del tuo business', icon: Icons.people_outlined, color: Colors.orange, - onTap: () => onOpenPage( - const CustomersContent(), - ), // Indice per CustomersContent + onTap: () => onOpenPage(const CustomersContent()), ), _buildHubCard( context, @@ -77,8 +78,9 @@ class MasterDataHubContent extends StatelessWidget { ), _buildHubCard( context, - title: 'Gestione Provider', - subtitle: 'Anagrafica mandati e servizi abilitati', + title: + 'Provider', // Accorciato per non andare a capo male su mobile + subtitle: 'Anagrafica mandati e servizi', icon: Icons.handshake_rounded, color: Colors.indigo, onTap: () { @@ -110,43 +112,43 @@ Widget _buildHubCard( return Card( clipBehavior: Clip.antiAlias, elevation: 2, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), // Un pelo più arrotondato + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: InkWell( onTap: onTap, child: Padding( - padding: const EdgeInsets.all( - 24.0, - ), // Aumentiamo il padding per dare respiro + // Ridotto da 22 a 16 per dare più respiro orizzontale su mobile + padding: const EdgeInsets.all(16.0), child: Column( - mainAxisAlignment: MainAxisAlignment.center, // CENTRA VERTICALMENTE - crossAxisAlignment: - CrossAxisAlignment.center, // CENTRA ORIZZONTALMENTE + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, children: [ - // Icona con un leggero sfondo circolare opaco per farla risaltare Container( padding: const EdgeInsets.all(12), decoration: BoxDecoration( - color: color.withValues(alpha: 0.1), // <--- API moderna + color: color.withValues(alpha: 0.1), shape: BoxShape.circle, ), - child: Icon(icon, size: 48, color: color), + child: Icon(icon, size: 40, color: color), ), - const SizedBox(height: 16), + const SizedBox(height: 12), // Leggermente ridotto Text( title, - textAlign: TextAlign.center, // Centra il testo + textAlign: TextAlign.center, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - const SizedBox(height: 8), - Text( - subtitle, - textAlign: TextAlign.center, // Centra il sottotitolo - style: TextStyle(fontSize: 13, color: Colors.grey.shade500), - maxLines: 2, + maxLines: 1, overflow: TextOverflow.ellipsis, ), + const SizedBox(height: 4), // Leggermente ridotto + Expanded( + // Impedisce matematicamente l'overflow verticale + child: Text( + subtitle, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 13, color: Colors.grey.shade500), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), ], ), ),