From 8d6e8647b18ac6aeb47ace8ac64a61ac89347d42 Mon Sep 17 00:00:00 2001 From: Mark M2 Macbook Date: Fri, 10 Apr 2026 10:47:56 +0200 Subject: [PATCH] refined responsive ui for dashboard and start customer work --- lib/core/routes/app_router.dart | 3 +- .../customers/blocs/customer_bloc.dart | 82 ++++++ .../customers/blocs/customer_events.dart | 26 ++ .../customers/blocs/customer_state.dart | 40 +++ .../customers/data/customer_repository.dart | 56 ++++ .../customers/models/customer_model.dart | 101 ++++++++ lib/features/home/ui/dashboard_content.dart | 242 ------------------ .../ui/dashboard_content.dart | 206 +++++++++++++++ .../ui/home_screen.dart | 52 ++-- lib/main.dart | 8 +- lib/ui/anagrafiche/anagrafiche_main_view.dart | 44 ---- lib/ui/dashboard/dashboard_view.dart | 119 --------- lib/ui/home_screen.dart | 67 ----- 13 files changed, 543 insertions(+), 503 deletions(-) create mode 100644 lib/features/customers/blocs/customer_bloc.dart create mode 100644 lib/features/customers/blocs/customer_events.dart create mode 100644 lib/features/customers/blocs/customer_state.dart create mode 100644 lib/features/customers/data/customer_repository.dart create mode 100644 lib/features/customers/models/customer_model.dart delete mode 100644 lib/features/home/ui/dashboard_content.dart create mode 100644 lib/features/home_and_dashboard/ui/dashboard_content.dart rename lib/features/{home => home_and_dashboard}/ui/home_screen.dart (69%) delete mode 100644 lib/ui/anagrafiche/anagrafiche_main_view.dart delete mode 100644 lib/ui/dashboard/dashboard_view.dart delete mode 100644 lib/ui/home_screen.dart diff --git a/lib/core/routes/app_router.dart b/lib/core/routes/app_router.dart index de19dd0..aaed85e 100644 --- a/lib/core/routes/app_router.dart +++ b/lib/core/routes/app_router.dart @@ -2,9 +2,8 @@ import 'package:flutter/material.dart'; import 'package:flux/core/blocs/session/session_bloc.dart'; import 'package:flux/features/auth/ui/auth_screen.dart'; import 'package:flux/features/company/ui/create_company_screen.dart'; -import 'package:flux/features/home/ui/dashboard_content.dart'; +import 'package:flux/features/home_and_dashboard/ui/home_screen.dart'; import 'package:flux/features/store/ui/create_store_screen.dart'; -import 'package:flux/ui/home_screen.dart'; import 'package:go_router/go_router.dart'; import 'dart:async'; diff --git a/lib/features/customers/blocs/customer_bloc.dart b/lib/features/customers/blocs/customer_bloc.dart new file mode 100644 index 0000000..8d70829 --- /dev/null +++ b/lib/features/customers/blocs/customer_bloc.dart @@ -0,0 +1,82 @@ +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flux/features/customers/data/customer_repository.dart'; +import 'package:flux/features/customers/models/customer_model.dart'; +import 'package:get_it/get_it.dart'; + +part 'customer_events.dart'; +part 'customer_state.dart'; + +class CustomerBloc extends Bloc { + final CustomerRepository _repository = GetIt.I(); + + CustomerBloc() : super(const CustomerState()) { + on(_onLoadCustomers); + on(_onCreateCustomer); + on(_onSearchCustomers); + } + + Future _onLoadCustomers( + LoadCustomersRequested event, + Emitter emit, + ) async { + emit(state.copyWith(status: CustomerStatus.loading)); + try { + final customers = await _repository.getCustomers(event.companyId); + emit( + state.copyWith(status: CustomerStatus.success, customers: customers), + ); + } catch (e) { + emit( + state.copyWith( + status: CustomerStatus.failure, + errorMessage: e.toString(), + ), + ); + } + } + + Future _onCreateCustomer( + CreateCustomerRequested event, + Emitter emit, + ) async { + emit(state.copyWith(status: CustomerStatus.loading)); + try { + final newCustomer = await _repository.createCustomer(event.customer); + + // Aggiorniamo la lista locale aggiungendo il nuovo cliente in cima + final updatedList = List.from(state.customers) + ..insert(0, newCustomer); + + emit( + state.copyWith( + status: CustomerStatus.success, + customers: updatedList, + lastCreatedCustomer: + newCustomer, // Lo passiamo per le Dialog "al volo" + ), + ); + } catch (e) { + emit( + state.copyWith( + status: CustomerStatus.failure, + errorMessage: e.toString(), + ), + ); + } + } + + Future _onSearchCustomers( + SearchCustomersRequested event, + Emitter emit, + ) async { + // Non mettiamo loading per evitare flickering durante la digitazione + try { + final results = await _repository.searchCustomers( + event.companyId, + event.query, + ); + emit(state.copyWith(status: CustomerStatus.success, customers: results)); + } catch (_) {} + } +} diff --git a/lib/features/customers/blocs/customer_events.dart b/lib/features/customers/blocs/customer_events.dart new file mode 100644 index 0000000..c031079 --- /dev/null +++ b/lib/features/customers/blocs/customer_events.dart @@ -0,0 +1,26 @@ +part of 'customer_bloc.dart'; + +abstract class CustomerEvent extends Equatable { + const CustomerEvent(); + @override + List get props => []; +} + +// Carica tutti i clienti dell'azienda +class LoadCustomersRequested extends CustomerEvent { + final String companyId; + const LoadCustomersRequested(this.companyId); +} + +// Crea un cliente (usato sia dalla lista che dalla Dialog operazioni) +class CreateCustomerRequested extends CustomerEvent { + final CustomerModel customer; + const CreateCustomerRequested(this.customer); +} + +// Ricerca in tempo reale +class SearchCustomersRequested extends CustomerEvent { + final String companyId; + final String query; + const SearchCustomersRequested(this.companyId, this.query); +} diff --git a/lib/features/customers/blocs/customer_state.dart b/lib/features/customers/blocs/customer_state.dart new file mode 100644 index 0000000..43134bf --- /dev/null +++ b/lib/features/customers/blocs/customer_state.dart @@ -0,0 +1,40 @@ +part of 'customer_bloc.dart'; + +enum CustomerStatus { initial, loading, success, failure } + +class CustomerState extends Equatable { + final CustomerStatus status; + final List customers; // Per la lista generale + final CustomerModel? + lastCreatedCustomer; // <--- Fondamentale per la Dialog "al volo" + final String? errorMessage; + + const CustomerState({ + this.status = CustomerStatus.initial, + this.customers = const [], + this.lastCreatedCustomer, + this.errorMessage, + }); + + CustomerState copyWith({ + CustomerStatus? status, + List? customers, + CustomerModel? lastCreatedCustomer, + String? errorMessage, + }) { + return CustomerState( + status: status ?? this.status, + customers: customers ?? this.customers, + lastCreatedCustomer: lastCreatedCustomer ?? this.lastCreatedCustomer, + errorMessage: errorMessage ?? this.errorMessage, + ); + } + + @override + List get props => [ + status, + customers, + lastCreatedCustomer, + errorMessage, + ]; +} diff --git a/lib/features/customers/data/customer_repository.dart b/lib/features/customers/data/customer_repository.dart new file mode 100644 index 0000000..f672751 --- /dev/null +++ b/lib/features/customers/data/customer_repository.dart @@ -0,0 +1,56 @@ +import 'package:get_it/get_it.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; +import '../models/customer_model.dart'; + +class CustomerRepository { + final SupabaseClient _client = GetIt.I(); + + // Crea un nuovo cliente + Future createCustomer(CustomerModel customer) async { + try { + final response = await _client + .from('customer') + .insert(customer.toJson()) + .select() + .single(); + return CustomerModel.fromJson(response); + } catch (e) { + throw 'Errore durante la creazione del cliente: $e'; + } + } + + // Recupera tutti i clienti dell'azienda + Future> getCustomers(String companyId) async { + try { + final response = await _client + .from('customer') + .select() + .eq('company_id', companyId) + .eq('is_active', true) + .order('nome'); + + return (response as List).map((c) => CustomerModel.fromJson(c)).toList(); + } catch (e) { + throw 'Errore nel recupero clienti'; + } + } + + // Ricerca clienti per nome o telefono (fondamentale per la UX) + Future> searchCustomers( + String companyId, + String query, + ) async { + try { + final response = await _client + .from('customer') + .select() + .eq('company_id', companyId) + .or('nome.ilike.%$query%,telefono.ilike.%$query%') + .limit(10); + + return (response as List).map((c) => CustomerModel.fromJson(c)).toList(); + } catch (e) { + return []; + } + } +} diff --git a/lib/features/customers/models/customer_model.dart b/lib/features/customers/models/customer_model.dart new file mode 100644 index 0000000..2478d8c --- /dev/null +++ b/lib/features/customers/models/customer_model.dart @@ -0,0 +1,101 @@ +import 'package:equatable/equatable.dart'; + +class CustomerModel extends Equatable { + final BigInt? id; // Bigint in SQL + final DateTime? createdAt; + final String nome; + final String telefono; + final String email; + final String note; + final DateTime? dataUltimoContatto; + final bool nonDisturbare; + final String companyId; // UUID + final bool isActive; + + const CustomerModel({ + this.id, + this.createdAt, + required this.nome, + required this.telefono, + required this.email, + required this.note, + this.dataUltimoContatto, + this.nonDisturbare = false, + required this.companyId, + this.isActive = true, + }); + + @override + List get props => [ + id, + createdAt, + nome, + telefono, + email, + note, + dataUltimoContatto, + nonDisturbare, + companyId, + isActive, + ]; + + CustomerModel copyWith({ + BigInt? id, + DateTime? createdAt, + String? nome, + String? telefono, + String? email, + String? note, + DateTime? dataUltimoContatto, + bool? nonDisturbare, + String? companyId, + bool? isActive, + }) { + return CustomerModel( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + nome: nome ?? this.nome, + telefono: telefono ?? this.telefono, + email: email ?? this.email, + note: note ?? this.note, + dataUltimoContatto: dataUltimoContatto ?? this.dataUltimoContatto, + nonDisturbare: nonDisturbare ?? this.nonDisturbare, + companyId: companyId ?? this.companyId, + isActive: isActive ?? this.isActive, + ); + } + + factory CustomerModel.fromJson(Map json) { + return CustomerModel( + id: json['id'], + createdAt: json['created_at'] != null + ? DateTime.parse(json['created_at']) + : null, + nome: json['nome'], + telefono: json['telefono'], + email: json['email'], + note: json['note'] ?? '', + dataUltimoContatto: json['data_ultimo_contatto'] != null + ? DateTime.parse(json['data_ultimo_contatto']) + : null, + nonDisturbare: json['non_disturbare'] ?? false, + companyId: json['company_id'], + isActive: json['is_active'] ?? true, + ); + } + + Map toJson() { + return { + if (id != null) 'id': id, + 'nome': nome, + 'telefono': telefono, + 'email': email, + 'note': note, + if (dataUltimoContatto != null) + 'data_ultimo_contatto': dataUltimoContatto!.toIso8601String(), + 'non_disturbare': nonDisturbare, + 'company_id': companyId, + 'is_active': isActive, + }; + } +} diff --git a/lib/features/home/ui/dashboard_content.dart b/lib/features/home/ui/dashboard_content.dart deleted file mode 100644 index ce5af56..0000000 --- a/lib/features/home/ui/dashboard_content.dart +++ /dev/null @@ -1,242 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flux/core/blocs/session/session_bloc.dart'; -import 'package:flux/core/theme/theme.dart'; - -class DashboardContent extends StatelessWidget { - final bool? isLargeScreen; - - const DashboardContent({super.key, this.isLargeScreen}); - - @override - Widget build(BuildContext context) { - // Ascoltiamo il SessionBloc per avere i dati in tempo reale - return BlocBuilder( - builder: (context, state) { - final store = state.selectedStore; - final company = state.company; - - return Scaffold( - body: CustomScrollView( - slivers: [ - // Un'AppBar elegante che si rimpicciolisce - SliverAppBar( - expandedHeight: 120.0, - floating: false, - pinned: true, - flexibleSpace: FlexibleSpaceBar( - title: Text( - store?.nome ?? 'Flux Dashboard', - style: TextStyle(color: context.primaryText), - ), - background: Container(color: context.background), - ), - actions: [ - IconButton( - icon: const Icon(Icons.settings_outlined), - onPressed: () => Navigator.pushNamed(context, '/settings'), - ), - ], - ), - - SliverToBoxAdapter( - child: Center( - child: Container( - constraints: const BoxConstraints( - maxWidth: 1200, - ), // Larghezza massima "confortevole" - padding: const EdgeInsets.all(24.0), - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildWelcomeHeader(context, company?.ragioneSociale), - const SizedBox(height: 30), - _SectionTitle(title: 'AZIONI RAPIDE'), - const SizedBox(height: 16), - _buildGridActions(context), - const SizedBox(height: 32), - _SectionTitle(title: 'STATO NEGOZIO'), - const SizedBox(height: 16), - _buildInfoCard(context, store), - ], - ), - ), - ), - ), - ), - ], - ), - ); - }, - ); - } - - Widget _buildWelcomeHeader(BuildContext context, String? companyName) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Bentornato,', - style: TextStyle(color: context.secondaryText, fontSize: 16), - ), - Text( - companyName ?? 'La tua Azienda', - style: TextStyle( - color: context.primaryText, - fontSize: 24, - fontWeight: FontWeight.bold, - ), - ), - ], - ); - } - - Widget _buildGridActions(BuildContext context) { - return LayoutBuilder( - builder: (context, constraints) { - // Calcoliamo il numero di colonne in base alla larghezza - // Sotto i 600px (mobile): 2 colonne - // Tra 600 e 1000px (tablet): 3 o 4 colonne - // Sopra i 1000px (desktop): 6 colonne - int crossAxisCount = 2; - if (constraints.maxWidth > 1000) { - crossAxisCount = 6; - } else if (constraints.maxWidth > 600) { - crossAxisCount = 4; - } - - return GridView.count( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - crossAxisCount: crossAxisCount, - mainAxisSpacing: 16, - crossAxisSpacing: 16, - // Su desktop rendiamo i tasti un po' più squadrati (1.0) - // Su mobile manteniamo il rettangolo (1.5) - childAspectRatio: constraints.maxWidth > 600 ? 1.2 : 1.5, - children: [ - _ActionCard( - label: 'Nuova Op', - icon: Icons.add_task_rounded, - color: context.accent, - onTap: () {}, - ), - _ActionCard( - label: 'Clienti', - icon: Icons.people_alt_rounded, - color: Colors.orange, - onTap: () {}, - ), - _ActionCard( - label: 'Campagne', - icon: Icons.campaign_rounded, - color: Colors.purple, - onTap: () {}, - ), - _ActionCard( - label: 'Report', - icon: Icons.analytics_rounded, - color: Colors.teal, - onTap: () {}, - ), - // Se siamo su desktop, possiamo aggiungere altri slot senza affollare - if (constraints.maxWidth > 600) ...[ - _ActionCard( - label: 'Impostazioni', - icon: Icons.settings, - color: Colors.grey, - onTap: () {}, - ), - _ActionCard( - label: 'Supporto', - icon: Icons.help_outline, - color: Colors.blueGrey, - onTap: () {}, - ), - ], - ], - ); - }, - ); - } - - Widget _buildInfoCard(BuildContext context, dynamic store) { - return Container( - width: double.infinity, - padding: const EdgeInsets.all(20), - decoration: BoxDecoration( - color: context.accent.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(20), - ), - child: Row( - children: [ - Icon(Icons.location_on_rounded, color: context.accent), - const SizedBox(width: 12), - Expanded( - child: Text( - '${store?.indirizzo}, ${store?.comune}', - style: TextStyle(color: context.primaryText), - ), - ), - ], - ), - ); - } -} - -class _ActionCard extends StatelessWidget { - final String label; - final IconData icon; - final Color color; - final VoidCallback onTap; - - const _ActionCard({ - required this.label, - required this.icon, - required this.color, - required this.onTap, - }); - - @override - Widget build(BuildContext context) { - return InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(20), - child: Container( - decoration: BoxDecoration( - color: context.background, - borderRadius: BorderRadius.circular(20), - border: Border.all(color: context.accent.withValues(alpha: 0.1)), - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon, color: color, size: 32), - const SizedBox(height: 8), - Text(label, style: const TextStyle(fontWeight: FontWeight.bold)), - ], - ), - ), - ); - } -} - -class _SectionTitle extends StatelessWidget { - final String title; - const _SectionTitle({required this.title}); - - @override - Widget build(BuildContext context) { - return Text( - title, - style: TextStyle( - color: context.secondaryText, - fontWeight: FontWeight.bold, - fontSize: 12, - letterSpacing: 1.1, - ), - ); - } -} diff --git a/lib/features/home_and_dashboard/ui/dashboard_content.dart b/lib/features/home_and_dashboard/ui/dashboard_content.dart new file mode 100644 index 0000000..ac7a7c0 --- /dev/null +++ b/lib/features/home_and_dashboard/ui/dashboard_content.dart @@ -0,0 +1,206 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flux/core/blocs/session/session_bloc.dart'; +import 'package:flux/core/theme/theme.dart'; + +class DashboardContent extends StatelessWidget { + final bool isLargeScreen; + const DashboardContent({super.key, this.isLargeScreen = false}); + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final store = state.selectedStore; + final company = state.company; + + return Scaffold( + backgroundColor: context.background, + body: CustomScrollView( + slivers: [ + SliverAppBar( + expandedHeight: 100.0, + floating: false, + pinned: true, + elevation: 0, + backgroundColor: context.background, + flexibleSpace: FlexibleSpaceBar( + titlePadding: const EdgeInsets.only(left: 24, bottom: 16), + title: Text( + store?.nome ?? 'Dashboard', + style: TextStyle( + color: context.primaryText, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + SliverToBoxAdapter( + child: Center( + child: Container( + constraints: const BoxConstraints(maxWidth: 1200), + padding: const EdgeInsets.all(24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildWelcome(context, company?.ragioneSociale), + const SizedBox(height: 32), + const _SectionTitle(title: 'AZIONI RAPIDE'), + const SizedBox(height: 16), + _buildAdaptiveGrid(context), + const SizedBox(height: 40), + const _SectionTitle(title: 'INFO PUNTO VENDITA'), + const SizedBox(height: 16), + _buildStoreCard(context, store), + ], + ), + ), + ), + ), + ], + ), + ); + }, + ); + } + + Widget _buildWelcome(BuildContext context, String? name) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Benvenuto in', + style: TextStyle(color: context.secondaryText, fontSize: 16), + ), + Text( + name ?? 'Azienda', + style: const TextStyle(fontSize: 28, fontWeight: FontWeight.w900), + ), + ], + ); + } + + Widget _buildAdaptiveGrid(BuildContext context) { + return LayoutBuilder( + builder: (context, constraints) { + // Logica Colonne: Mobile 2, Tablet 3, Desktop 4+ + int crossAxisCount = 2; + if (constraints.maxWidth > 1000) { + crossAxisCount = 5; + } else if (constraints.maxWidth > 700) { + crossAxisCount = 3; + } + + return GridView.count( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + crossAxisCount: crossAxisCount, + mainAxisSpacing: 16, + crossAxisSpacing: 16, + childAspectRatio: isLargeScreen ? 1.3 : 1.5, + children: [ + _ActionCard( + label: 'Nuova Op', + icon: Icons.add_task, + color: context.accent, + onTap: () {}, + ), + _ActionCard( + label: 'Clienti', + icon: Icons.people, + color: Colors.orange, + onTap: () {}, + ), + _ActionCard( + label: 'Campagne', + icon: Icons.campaign, + color: Colors.purple, + onTap: () {}, + ), + _ActionCard( + label: 'Report', + icon: Icons.analytics, + color: Colors.teal, + onTap: () {}, + ), + ], + ); + }, + ); + } + + Widget _buildStoreCard(BuildContext context, dynamic store) { + return Container( + padding: const EdgeInsets.all(20), + decoration: BoxDecoration( + color: context.accent.withValues(alpha: 0.05), + borderRadius: BorderRadius.circular(16), + border: Border.all(color: context.accent.withValues(alpha: 0.1)), + ), + child: Row( + children: [ + Icon(Icons.location_on, color: context.accent), + const SizedBox(width: 16), + Text('${store?.indirizzo}, ${store?.comune} (${store?.provincia})'), + ], + ), + ); + } +} + +// Widget di supporto rimasti invariati (ActionCard e SectionTitle) +class _ActionCard extends StatelessWidget { + final String label; + final IconData icon; + final Color color; + final VoidCallback onTap; + + const _ActionCard({ + required this.label, + required this.icon, + required this.color, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return Card( + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + // CAMBIA QUI: da Border.all a BorderSide + side: BorderSide( + color: context.accent.withValues(alpha: 0.1), + width: 1, + ), + ), + child: InkWell( + onTap: onTap, + borderRadius: BorderRadius.circular(16), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(icon, color: color, size: 32), + const SizedBox(height: 8), + Text(label, style: const TextStyle(fontWeight: FontWeight.bold)), + ], + ), + ), + ); + } +} + +class _SectionTitle extends StatelessWidget { + final String title; + const _SectionTitle({required this.title}); + @override + Widget build(BuildContext context) => Text( + title, + style: TextStyle( + color: context.accent, + fontWeight: FontWeight.bold, + fontSize: 12, + letterSpacing: 1.2, + ), + ); +} diff --git a/lib/features/home/ui/home_screen.dart b/lib/features/home_and_dashboard/ui/home_screen.dart similarity index 69% rename from lib/features/home/ui/home_screen.dart rename to lib/features/home_and_dashboard/ui/home_screen.dart index e24d4f4..bbae7c1 100644 --- a/lib/features/home/ui/home_screen.dart +++ b/lib/features/home_and_dashboard/ui/home_screen.dart @@ -2,7 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flux/core/blocs/session/session_bloc.dart'; import 'package:flux/core/theme/theme.dart'; -import 'package:flux/features/home/ui/dashboard_content.dart'; +import 'dashboard_content.dart'; // Importiamo il contenuto della dashboard class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -20,25 +20,28 @@ class _HomeScreenState extends State { builder: (context, state) { return LayoutBuilder( builder: (context, constraints) { - // Definiamo se siamo su uno schermo "Large" (es. sopra i 900px) + // Se lo schermo è più largo di 900px usiamo il layout Desktop final bool isLargeScreen = constraints.maxWidth > 900; return Scaffold( body: Row( children: [ - // --- SIDEBAR (Solo per schermi grandi) --- + // --- SIDEBAR (Solo Desktop/Tablet) --- if (isLargeScreen) NavigationRail( selectedIndex: _selectedIndex, - onDestinationSelected: (int index) { - setState(() => _selectedIndex = index); - }, - extended: - constraints.maxWidth > - 1200, // Si allarga se c'è molto spazio - labelType: constraints.maxWidth > 1200 - ? NavigationRailLabelType.none - : NavigationRailLabelType.all, + onDestinationSelected: (index) => + setState(() => _selectedIndex = index), + extended: constraints.maxWidth > 1200, + backgroundColor: context.background, + selectedIconTheme: IconThemeData(color: context.accent), + selectedLabelTextStyle: TextStyle( + color: context.accent, + fontWeight: FontWeight.bold, + ), + unselectedLabelTextStyle: TextStyle( + color: context.secondaryText, + ), leading: _buildRailHeader(constraints.maxWidth > 1200), destinations: const [ NavigationRailDestination( @@ -59,18 +62,20 @@ class _HomeScreenState extends State { ], ), - // --- CONTENUTO PRINCIPALE --- + // --- CONTENUTO DINAMICO --- Expanded( - child: _buildMainContent(context, state, isLargeScreen), + child: _buildPageContent(_selectedIndex, isLargeScreen), ), ], ), - // --- BOTTOM NAVIGATION (Solo per Mobile) --- + // --- BOTTOM BAR (Solo Mobile) --- bottomNavigationBar: isLargeScreen ? null : BottomNavigationBar( currentIndex: _selectedIndex, onTap: (index) => setState(() => _selectedIndex = index), + selectedItemColor: context.accent, + unselectedItemColor: context.secondaryText, items: const [ BottomNavigationBarItem( icon: Icon(Icons.dashboard), @@ -93,7 +98,6 @@ class _HomeScreenState extends State { ); } - // Header per la Sidebar (Logo o Icona) Widget _buildRailHeader(bool isExtended) { return Padding( padding: const EdgeInsets.symmetric(vertical: 24), @@ -110,19 +114,17 @@ class _HomeScreenState extends State { ); } - Widget _buildMainContent( - BuildContext context, - SessionState state, - bool isLargeScreen, - ) { - // Qui gestiamo lo switch tra le pagine - switch (_selectedIndex) { + // Switch tra le sottopagine + Widget _buildPageContent(int index, bool isLargeScreen) { + switch (index) { case 0: return DashboardContent(isLargeScreen: isLargeScreen); case 1: - return const Center(child: Text('Pagina Clienti')); // La faremo! + return const Center(child: Text('Pagina Clienti (Coming Soon)')); + case 2: + return const Center(child: Text('Pagina Operazioni (Coming Soon)')); default: - return const DashboardContent(); + return DashboardContent(isLargeScreen: isLargeScreen); } } } diff --git a/lib/main.dart b/lib/main.dart index b5283c2..ad9ddaf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -5,14 +5,12 @@ import 'package:flux/core/routes/app_router.dart'; import 'package:flux/core/theme/theme.dart'; import 'package:flux/core/theme/bloc/theme_bloc.dart'; import 'package:flux/features/auth/bloc/auth_bloc.dart'; -import 'package:flux/features/auth/ui/auth_screen.dart'; import 'package:flux/features/company/bloc/company_bloc.dart'; import 'package:flux/features/company/data/company_repository.dart'; -import 'package:flux/features/company/ui/create_company_screen.dart'; +import 'package:flux/features/customers/blocs/customer_bloc.dart'; +import 'package:flux/features/customers/data/customer_repository.dart'; import 'package:flux/features/store/bloc/store_bloc.dart'; import 'package:flux/features/store/data/store_repository.dart'; -import 'package:flux/features/store/ui/create_store_screen.dart'; -import 'package:flux/ui/home_screen.dart'; import 'package:flux/features/settings/settings.dart'; import 'package:get_it/get_it.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -34,6 +32,7 @@ void main() async { BlocProvider(create: (context) => AuthBloc()), BlocProvider(create: (context) => CompanyBloc()), BlocProvider(create: (context) => StoreBloc()), + BlocProvider(create: (context) => CustomerBloc()), ], child: const FluxApp(), ), @@ -55,6 +54,7 @@ Future setupLocator() async { getIt.registerLazySingleton(() => AppSettings()); getIt.registerLazySingleton(() => CompanyRepository()); getIt.registerLazySingleton(() => StoreRepository()); + getIt.registerLazySingleton(() => CustomerRepository()); } class FluxApp extends StatelessWidget { diff --git a/lib/ui/anagrafiche/anagrafiche_main_view.dart b/lib/ui/anagrafiche/anagrafiche_main_view.dart deleted file mode 100644 index 822ba10..0000000 --- a/lib/ui/anagrafiche/anagrafiche_main_view.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flux/core/theme/theme.dart'; - -class AnagraficheMainView extends StatelessWidget { - const AnagraficheMainView({super.key}); - - @override - Widget build(BuildContext context) { - // Controller locale per gestire i Tab - return DefaultTabController( - length: 4, - child: Scaffold( - appBar: AppBar( - title: const Text('Anagrafiche'), - bottom: TabBar( - isScrollable: true, - indicatorColor: FluxColors.accentTurquoise, - labelColor: FluxColors.accentTurquoise, - unselectedLabelColor: Theme.of(context).textTheme.bodyMedium?.color, - tabs: [ - Tab(icon: Icon(Icons.storefront), text: 'Negozi'), - Tab(icon: Icon(Icons.support_agent), text: 'Gestori'), - Tab(icon: Icon(Icons.assignment_ind), text: 'Clienti'), - Tab(icon: Icon(Icons.phone_android), text: 'Prodotti'), - ], - ), - ), - body: const TabBarView( - children: [ - // Esempi di view iniettate con Bloc dedicati (da implementare) - /* ElencoEntitaView(tipoEntita: 'Negozi'), // Provider... - ElencoEntitaView(tipoEntita: 'Gestori'), - ElencoEntitaView(tipoEntita: 'Clienti'), - ElencoEntitaView(tipoEntita: 'Prodotti'), */ - Placeholder(), - Placeholder(), - Placeholder(), - Placeholder(), - ], - ), - ), - ); - } -} diff --git a/lib/ui/dashboard/dashboard_view.dart b/lib/ui/dashboard/dashboard_view.dart deleted file mode 100644 index 807241a..0000000 --- a/lib/ui/dashboard/dashboard_view.dart +++ /dev/null @@ -1,119 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flux/core/theme/theme.dart'; - -class DashboardView extends StatelessWidget { - const DashboardView({super.key}); - - @override - Widget build(BuildContext context) { - // Iniezione del Bloc per la creazione di operazioni (da implementare) - return /* BlocProvider( - create: (context) => OperazioneBloc(), // Implementa la logica nel Bloc - child: */ Scaffold( - appBar: AppBar(title: Text('FLUX')), - body: SingleChildScrollView( - padding: EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _WelcomeHeader(), - SizedBox(height: 24), - _QuickActions(), // Contiene "Nuova Operazione" - SizedBox(height: 24), - _RecentActivityPreview(), - ], - ), - ), - ); - // ); - } -} - -class _WelcomeHeader extends StatelessWidget { - const _WelcomeHeader(); - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text('Benvenuto,', style: Theme.of(context).textTheme.bodyMedium), - Text( - 'Negozio Piacenza Centro', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ); - } -} - -class _QuickActions extends StatelessWidget { - const _QuickActions(); - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'AZIONI RAPIDE', - style: Theme.of( - context, - ).textTheme.titleSmall?.copyWith(color: FluxColors.accentTurquoise), - ), - const SizedBox(height: 12), - ElevatedButton.icon( - onPressed: () { - // Emetti evento al Bloc: BlocProvider.of(context).add(IniziaNuovaOperazione()); - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar(content: Text('Avvio Nuova Operazione...')), - ); - }, - icon: const Icon(Icons.add), - label: const Text('NUOVA OPERAZIONE TELCO'), - style: ElevatedButton.styleFrom( - minimumSize: const Size(double.infinity, 50), - ), - ), - ], - ); - } -} - -class _RecentActivityPreview extends StatelessWidget { - const _RecentActivityPreview(); - @override - Widget build(BuildContext context) { - return Card( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - 'Attività Recenti', - style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - ), - Divider(color: Theme.of(context).textTheme.bodyMedium?.color), - // Sostituire con BlocBuilder - _activityTile('Nuova Linea', 'Mario Rossi', '10 min fa', context), - _activityTile('Assistenza Tech', 'iPhone 13', '45 min fa', context), - ], - ), - ), - ); - } - - Widget _activityTile( - String title, - String subtitle, - String time, - BuildContext context, - ) { - return ListTile( - contentPadding: EdgeInsets.zero, - leading: const Icon(Icons.history, color: FluxColors.accentTurquoise), - title: Text(title, style: Theme.of(context).textTheme.titleLarge), - subtitle: Text(subtitle), - trailing: Text(time, style: Theme.of(context).textTheme.bodyMedium), - ); - } -} diff --git a/lib/ui/home_screen.dart b/lib/ui/home_screen.dart deleted file mode 100644 index b689616..0000000 --- a/lib/ui/home_screen.dart +++ /dev/null @@ -1,67 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flux/core/theme/theme.dart'; -import 'package:flux/ui/anagrafiche/anagrafiche_main_view.dart'; -import 'package:flux/ui/dashboard/dashboard_view.dart'; -import 'package:flux/features/settings/settings_view.dart'; - -class HomeScreen extends StatefulWidget { - const HomeScreen({super.key}); - - @override - State createState() => _HomeScreenState(); -} - -class _HomeScreenState extends State { - int _selectedIndex = 0; - - static const List _widgetOptions = [ - DashboardView(), // Contiene Nuova Operazione - Placeholder(), - AnagraficheMainView(), // Gestisce [negozi, gestori, clienti, prodotti] - SettingsView(), - ]; - - void _onItemTapped(int index) { - setState(() { - _selectedIndex = index; - }); - } - - @override - Widget build(BuildContext context) { - final surfaceColor = Theme.of(context).colorScheme.surface; - return Scaffold( - body: Center(child: _widgetOptions.elementAt(_selectedIndex)), - bottomNavigationBar: BottomNavigationBar( - items: const [ - BottomNavigationBarItem( - icon: Icon(Icons.dashboard_outlined), - activeIcon: Icon(Icons.dashboard), - label: 'Dashboard', - ), - BottomNavigationBarItem( - icon: Icon(Icons.history_edu_outlined), - activeIcon: Icon(Icons.history_edu), - label: 'Operazioni', - ), - BottomNavigationBarItem( - icon: Icon(Icons.people_alt_outlined), - activeIcon: Icon(Icons.people_alt), - label: 'Anagrafiche', - ), - BottomNavigationBarItem( - icon: Icon(Icons.settings_outlined), - activeIcon: Icon(Icons.settings), - label: 'Impostazioni', - ), - ], - currentIndex: _selectedIndex, - selectedItemColor: FluxColors.accentTurquoise, - unselectedItemColor: Theme.of(context).textTheme.bodyMedium?.color, - backgroundColor: surfaceColor, - type: BottomNavigationBarType.fixed, - onTap: _onItemTapped, - ), - ); - } -}