diff --git a/lib/core/routes/app_router.dart b/lib/core/routes/app_router.dart index f824ef1..09e1076 100644 --- a/lib/core/routes/app_router.dart +++ b/lib/core/routes/app_router.dart @@ -18,6 +18,9 @@ import 'package:flux/features/customers/models/customer_model.dart'; import 'package:flux/features/customers/ui/customer_detail_screen.dart'; import 'package:flux/features/customers/ui/customer_form_screen.dart'; import 'package:flux/features/customers/ui/customers_list_screen.dart'; +import 'package:flux/features/home/dashboard_store_operation_list/bloc/dashboard_store_operation_list_cubit.dart'; +import 'package:flux/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_cubit.dart'; +import 'package:flux/features/home/dashboard_task_list/blocs/dashboard_task_list_cubit.dart'; import 'package:flux/features/home/ui/home_screen.dart'; import 'package:flux/features/master_data/master_data_hub_content.dart'; import 'package:flux/features/master_data/products/blocs/product_cubit.dart'; @@ -142,7 +145,29 @@ class AppRouter { path: '/', name: Routes.home, builder: (context, state) { - return const HomeScreen(); + return MultiBlocProvider( + providers: [ + BlocProvider( + create: (context) => DashboardStoreOperationListCubit( + companyId: sessionCubit.state.company?.id, + storeId: sessionCubit.state.currentStore?.id, + ), + ), + BlocProvider( + create: (context) => DashboardTaskListCubit( + companyId: sessionCubit.state.company?.id, + staffId: sessionCubit.state.currentStaffMember?.id, + ), + ), + BlocProvider( + create: (context) => DashboardStoreTicketListCubit( + companyId: sessionCubit.state.company?.id, + storeId: sessionCubit.state.currentStore?.id, + ), + ), + ], + child: const HomeScreen(), + ); }, ), diff --git a/lib/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_cubit.dart b/lib/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_cubit.dart new file mode 100644 index 0000000..e6fe6cb --- /dev/null +++ b/lib/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_cubit.dart @@ -0,0 +1,79 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flux/features/tickets/data/ticket_repository.dart'; +import 'package:flux/features/tickets/models/ticket_model.dart'; +import 'package:get_it/get_it.dart'; + +part 'dashboard_store_ticket_list_state.dart'; + +class DashboardStoreTicketListCubit + extends Cubit { + final TicketRepository _repository = GetIt.I.get(); + final String? companyId; + final String? storeId; + StreamSubscription? _subscription; + + DashboardStoreTicketListCubit({ + required this.companyId, + required this.storeId, + }) : super( + const DashboardStoreTicketListState( + status: DashboardStoreTicketListStatus.initial, + ), + ); + + void stopListening() { + _subscription?.cancel(); + _subscription = null; + } + + void startListening() { + stopListening(); + + emit(state.copyWith(status: DashboardStoreTicketListStatus.loading)); + + // Primo caricamento + _loadTicketsSilently(); + + // Inizio ascolto campanello + try { + _subscription = _repository + .getLatestStoreTicketsStream(storeId: storeId!, limit: 10) + .listen((_) { + // Quando il campanello suona (qualcosa è cambiato a DB), ricarichiamo! + _loadTicketsSilently(); + }); + } on Exception catch (e) { + debugPrint(e.toString()); + } + } + + Future _loadTicketsSilently() async { + try { + final tickets = await _repository.fetchTickets( + companyId: companyId!, + storeId: storeId, + limit: 10, + offset: 0, + ); + + emit( + state.copyWith( + status: DashboardStoreTicketListStatus.success, + tickets: tickets, + errorMessage: null, + ), + ); + } catch (e) { + emit( + state.copyWith( + status: DashboardStoreTicketListStatus.failure, + errorMessage: e.toString(), + ), + ); + } + } +} diff --git a/lib/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_state.dart b/lib/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_state.dart new file mode 100644 index 0000000..bdc4c0c --- /dev/null +++ b/lib/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_state.dart @@ -0,0 +1,30 @@ +part of 'dashboard_store_ticket_list_cubit.dart'; + +enum DashboardStoreTicketListStatus { initial, loading, success, failure } + +class DashboardStoreTicketListState extends Equatable { + final DashboardStoreTicketListStatus status; + final List tickets; + final String? errorMessage; + + const DashboardStoreTicketListState({ + this.status = DashboardStoreTicketListStatus.initial, + this.tickets = const [], + this.errorMessage, + }); + + DashboardStoreTicketListState copyWith({ + DashboardStoreTicketListStatus? status, + List? tickets, + String? errorMessage, + }) { + return DashboardStoreTicketListState( + status: status ?? this.status, + tickets: tickets ?? this.tickets, + errorMessage: errorMessage ?? this.errorMessage, + ); + } + + @override + List get props => [status, tickets, errorMessage]; +} diff --git a/lib/features/home/dashboard_store_ticket_list/ui/dashboard_store_ticket_list_card.dart b/lib/features/home/dashboard_store_ticket_list/ui/dashboard_store_ticket_list_card.dart new file mode 100644 index 0000000..4712c9f --- /dev/null +++ b/lib/features/home/dashboard_store_ticket_list/ui/dashboard_store_ticket_list_card.dart @@ -0,0 +1,189 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flux/core/routes/routes.dart'; +import 'package:flux/core/theme/theme.dart'; +import 'package:flux/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_cubit.dart'; +import 'package:flux/features/tickets/models/ticket_status_extension.dart'; +import 'package:go_router/go_router.dart'; + +class DashboardStoreTicketListCard extends StatelessWidget { + const DashboardStoreTicketListCard({super.key}); + + @override + Widget build(BuildContext context) { + return _DashboardStoreTicketListCardContent(); + } +} + +class _DashboardStoreTicketListCardContent extends StatelessWidget { + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + const color = Colors.blue; + + return Card( + elevation: 0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16), + side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)), + ), + child: InkWell( + onTap: () => context.pushNamed(Routes.tickets), + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // --- HEADER DELLA CARD --- + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), + ), + child: const Icon( + Icons.design_services_outlined, + color: color, + size: 20, + ), + ), + const SizedBox(width: 12), + Expanded( + child: Text( + "Ticket recenti", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: context.primaryText, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 12), + + // --- CORPO DELLA CARD (LA LISTA REAL-TIME) --- + Expanded( + child: + BlocBuilder< + DashboardStoreTicketListCubit, + DashboardStoreTicketListState + >( + builder: (context, state) { + if (state.status == + DashboardStoreTicketListStatus.loading || + state.status == + DashboardStoreTicketListStatus.initial) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + if (state.status == + DashboardStoreTicketListStatus.failure) { + return Center( + child: Text( + "Errore di caricamento", + style: TextStyle(color: theme.colorScheme.error), + ), + ); + } + + if (state.tickets.isEmpty) { + return Center( + child: Text( + "Nessun ticket recente.", + style: TextStyle( + color: context.secondaryText, + fontStyle: FontStyle.italic, + ), + ), + ); + } + + return ListView.separated( + itemCount: state.tickets.length, + separatorBuilder: (context, index) => Divider( + height: 1, + color: theme.dividerColor.withValues(alpha: 0.3), + ), + itemBuilder: (context, index) { + final ticket = state.tickets[index]; + final statusColor = ticket.ticketStatus.color; + return InkWell( + onTap: () => context.pushNamed( + Routes.ticketForm, + extra: (createdBy: null, ticket: ticket), + pathParameters: {'id': ticket.id!}, + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Container( + width: 8, + height: + 30, // Un'altezza fissa per farlo comparire! + decoration: BoxDecoration( + color: statusColor, + borderRadius: BorderRadius.circular( + 4, + ), // Angoli smussati per stile + ), + ), + const SizedBox(width: 4), + Expanded( + flex: 5, + child: Text( + ticket.customer?.name ?? + 'Cliente sconosciuto', + style: TextStyle( + fontWeight: FontWeight.w700, + color: context.primaryText, + ), + ), + ), + Expanded( + flex: 5, + child: Text( + ticket.targetModelName ?? + 'Modello sconosciuto', + style: TextStyle( + fontWeight: FontWeight.w600, + color: context.primaryText, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Text( + "${ticket.createdAt?.day}/${ticket.createdAt?.month}", + style: TextStyle( + color: context.secondaryText, + fontSize: 12, + ), + ), + ], + ), + ), + ); + }, + ); + }, + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/features/home/dashboard_task_list/blocs/dashboard_task_list_cubit.dart b/lib/features/home/dashboard_task_list/blocs/dashboard_task_list_cubit.dart index b48a722..fa320f9 100644 --- a/lib/features/home/dashboard_task_list/blocs/dashboard_task_list_cubit.dart +++ b/lib/features/home/dashboard_task_list/blocs/dashboard_task_list_cubit.dart @@ -20,6 +20,8 @@ class DashboardTaskListCubit extends Cubit { : super(const DashboardTaskListState()); void startListening() { + stopListening(); + emit(state.copyWith(status: DashboardTaskListStatus.loading)); // Primo caricamento diff --git a/lib/features/home/dashboard_task_list/ui/dashboard_tasks_card.dart b/lib/features/home/dashboard_task_list/ui/dashboard_tasks_card.dart index 9bbde6a..b653fe2 100644 --- a/lib/features/home/dashboard_task_list/ui/dashboard_tasks_card.dart +++ b/lib/features/home/dashboard_task_list/ui/dashboard_tasks_card.dart @@ -1,10 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/routes/routes.dart'; import 'package:flux/core/theme/theme.dart'; import 'package:flux/features/home/dashboard_task_list/blocs/dashboard_task_list_cubit.dart'; -import 'package:get_it/get_it.dart'; import 'package:go_router/go_router.dart'; import 'package:flux/features/tasks/models/task_status.dart'; @@ -13,24 +11,7 @@ class DashboardTasksCard extends StatelessWidget { @override Widget build(BuildContext context) { - // Recuperiamo lo staff (o l'utente) loggato - // Adatta il getter in base a come è strutturato il tuo SessionState - final currentStaffId = GetIt.I - .get() - .state - .currentStaffMember - ?.id; - final companyId = GetIt.I.get().state.company!.id!; - - if (currentStaffId == null) { - return const SizedBox.shrink(); // Sicurezza se lo stato non è pronto - } - - return BlocProvider( - create: (context) => - DashboardTaskListCubit(staffId: currentStaffId, companyId: companyId), - child: const _DashboardTasksCardContent(), - ); + return _DashboardTasksCardContent(); } } diff --git a/lib/features/home/latest_store_tickets/blocs/latest_store_tickets_bloc.dart b/lib/features/home/latest_store_tickets/blocs/latest_store_tickets_bloc.dart deleted file mode 100644 index 4aa3e22..0000000 --- a/lib/features/home/latest_store_tickets/blocs/latest_store_tickets_bloc.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:equatable/equatable.dart'; -import 'package:flux/features/tickets/data/ticket_repository.dart'; -import 'package:flux/features/tickets/models/ticket_model.dart'; -import 'package:get_it/get_it.dart'; - -part 'latest_store_tickets_events.dart'; -part 'latest_store_tickets_state.dart'; - -class LatestStoreTicketsBloc - extends Bloc { - final _repository = GetIt.I.get(); - LatestStoreTicketsBloc() - : super( - const LatestStoreTicketsState(status: LatestStoreTicketsStatus.initial), - ) { - on((event, emit) async { - emit(state.copyWith(status: LatestStoreTicketsStatus.loading)); - try { - final hydratedStream = _repository - .getLatestStoreTicketsStream(storeId: event.storeId, limit: 10) - .asyncMap((List rawTickets) async { - List fullyHydratedTickets = []; - - for (TicketModel ticket in rawTickets) { - TicketModel fullTicket = await _repository.getTicketById( - ticket.id!, - ); - fullyHydratedTickets.add(fullTicket); - } - - return fullyHydratedTickets; - }); - await emit.forEach( - hydratedStream, - onData: (List fullyHydratedTickets) { - return state.copyWith( - tickets: fullyHydratedTickets, - status: LatestStoreTicketsStatus.success, - ); - }, - onError: (error, stackTrace) => state.copyWith( - status: LatestStoreTicketsStatus.failure, - error: error.toString(), - ), - ); - } catch (e) { - emit( - state.copyWith( - status: LatestStoreTicketsStatus.failure, - error: e.toString(), - ), - ); - } - }); - } -} diff --git a/lib/features/home/latest_store_tickets/blocs/latest_store_tickets_events.dart b/lib/features/home/latest_store_tickets/blocs/latest_store_tickets_events.dart deleted file mode 100644 index 48499ef..0000000 --- a/lib/features/home/latest_store_tickets/blocs/latest_store_tickets_events.dart +++ /dev/null @@ -1,17 +0,0 @@ -part of 'latest_store_tickets_bloc.dart'; - -abstract class LatestStoreTicketsEvent extends Equatable { - const LatestStoreTicketsEvent(); - - @override - List get props => []; -} - -class InitLatestStoreTicketsEvent extends LatestStoreTicketsEvent { - final String storeId; - - const InitLatestStoreTicketsEvent(this.storeId); - - @override - List get props => [storeId]; -} diff --git a/lib/features/home/latest_store_tickets/blocs/latest_store_tickets_state.dart b/lib/features/home/latest_store_tickets/blocs/latest_store_tickets_state.dart deleted file mode 100644 index c3b67a2..0000000 --- a/lib/features/home/latest_store_tickets/blocs/latest_store_tickets_state.dart +++ /dev/null @@ -1,29 +0,0 @@ -part of 'latest_store_tickets_bloc.dart'; - -enum LatestStoreTicketsStatus { initial, loading, success, failure } - -class LatestStoreTicketsState extends Equatable { - final LatestStoreTicketsStatus status; - final String? error; - final List tickets; - const LatestStoreTicketsState({ - required this.status, - this.error, - this.tickets = const [], - }); - - @override - List get props => [status, error, tickets]; - - LatestStoreTicketsState copyWith({ - LatestStoreTicketsStatus? status, - String? error, - List? tickets, - }) { - return LatestStoreTicketsState( - status: status ?? this.status, - error: error, - tickets: tickets ?? this.tickets, - ); - } -} diff --git a/lib/features/home/latest_store_tickets/ui/latest_store_tickets_card.dart b/lib/features/home/latest_store_tickets/ui/latest_store_tickets_card.dart deleted file mode 100644 index 1470278..0000000 --- a/lib/features/home/latest_store_tickets/ui/latest_store_tickets_card.dart +++ /dev/null @@ -1,198 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:flux/core/blocs/session/session_cubit.dart'; -import 'package:flux/core/routes/routes.dart'; -import 'package:flux/core/theme/theme.dart'; -import 'package:flux/features/home/latest_store_tickets/blocs/latest_store_tickets_bloc.dart'; -import 'package:flux/features/tickets/models/ticket_status_extension.dart'; -import 'package:go_router/go_router.dart'; - -class LatestStoreTicketsCard extends StatelessWidget { - const LatestStoreTicketsCard({super.key}); - - @override - Widget build(BuildContext context) { - final currentStoreId = context.read().state.currentStore?.id; - - return BlocProvider( - // 1. Creiamo il Bloc e facciamo partire subito la query - create: (context) => - LatestStoreTicketsBloc() - ..add(InitLatestStoreTicketsEvent(currentStoreId ?? '')), - child: BlocListener( - // 2. MAGIA: Se l'utente cambia negozio dalla barra in alto, riavviamo lo stream! - listenWhen: (previous, current) => - previous.currentStore?.id != current.currentStore?.id, - listener: (context, state) { - if (state.currentStore?.id != null) { - context.read().add( - InitLatestStoreTicketsEvent(state.currentStore!.id!), - ); - } - }, - child: _LatestStoreTicketsCardContent(), - ), - ); - } -} - -class _LatestStoreTicketsCardContent extends StatelessWidget { - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - const color = Colors.blue; - - return Card( - elevation: 0, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)), - ), - child: InkWell( - onTap: () => context.pushNamed(Routes.tickets), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // --- HEADER DELLA CARD --- - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: color.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - ), - child: const Icon( - Icons.design_services_outlined, - color: color, - size: 20, - ), - ), - const SizedBox(width: 12), - Expanded( - child: Text( - "Ticket recenti", - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: context.primaryText, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - ], - ), - const SizedBox(height: 12), - - // --- CORPO DELLA CARD (LA LISTA REAL-TIME) --- - Expanded( - child: BlocBuilder( - builder: (context, state) { - if (state.status == LatestStoreTicketsStatus.loading || - state.status == LatestStoreTicketsStatus.initial) { - return const Center(child: CircularProgressIndicator()); - } - - if (state.status == LatestStoreTicketsStatus.failure) { - return Center( - child: Text( - "Errore di caricamento", - style: TextStyle(color: theme.colorScheme.error), - ), - ); - } - - if (state.tickets.isEmpty) { - return Center( - child: Text( - "Nessun ticket recente.", - style: TextStyle( - color: context.secondaryText, - fontStyle: FontStyle.italic, - ), - ), - ); - } - - return ListView.separated( - itemCount: state.tickets.length, - separatorBuilder: (context, index) => Divider( - height: 1, - color: theme.dividerColor.withValues(alpha: 0.3), - ), - itemBuilder: (context, index) { - final ticket = state.tickets[index]; - final statusColor = ticket.ticketStatus.color; - return InkWell( - onTap: () => context.pushNamed( - Routes.ticketForm, - extra: (createdBy: null, ticket: ticket), - pathParameters: {'id': ticket.id!}, - ), - child: Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: 8, - height: - 30, // Un'altezza fissa per farlo comparire! - decoration: BoxDecoration( - color: statusColor, - borderRadius: BorderRadius.circular( - 4, - ), // Angoli smussati per stile - ), - ), - const SizedBox(width: 4), - Expanded( - flex: 5, - child: Text( - ticket.customer?.name ?? - 'Cliente sconosciuto', - style: TextStyle( - fontWeight: FontWeight.w700, - color: context.primaryText, - ), - ), - ), - Expanded( - flex: 5, - child: Text( - ticket.targetModelName ?? - 'Modello sconosciuto', - style: TextStyle( - fontWeight: FontWeight.w600, - color: context.primaryText, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - Text( - "${ticket.createdAt?.day}/${ticket.createdAt?.month}", - style: TextStyle( - color: context.secondaryText, - fontSize: 12, - ), - ), - ], - ), - ), - ); - }, - ); - }, - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/features/home/ui/home_screen.dart b/lib/features/home/ui/home_screen.dart index 2e77d59..fb3b91f 100644 --- a/lib/features/home/ui/home_screen.dart +++ b/lib/features/home/ui/home_screen.dart @@ -6,10 +6,11 @@ import 'package:flux/core/theme/theme.dart'; import 'package:flux/core/utils/extensions.dart'; import 'package:flux/core/widgets/staff_selector_modal.dart'; import 'package:flux/features/home/dashboard_store_operation_list/bloc/dashboard_store_operation_list_cubit.dart'; +import 'package:flux/features/home/dashboard_store_ticket_list/blocs/dashboard_store_ticket_list_cubit.dart'; +import 'package:flux/features/home/dashboard_store_ticket_list/ui/dashboard_store_ticket_list_card.dart'; import 'package:flux/features/home/dashboard_task_list/blocs/dashboard_task_list_cubit.dart'; import 'package:flux/features/home/dashboard_task_list/ui/dashboard_tasks_card.dart'; import 'package:flux/features/home/dashboard_store_operation_list/ui/latest_store_operations_card.dart'; -import 'package:flux/features/home/latest_store_tickets/ui/latest_store_tickets_card.dart'; import 'package:flux/features/home/ui/quick_actions_widget.dart'; import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart'; import 'package:flux/features/master_data/staff/models/staff_member_model.dart'; @@ -38,22 +39,31 @@ class _HomeScreenState extends State { onPause: () { // L'utente ha messo l'app in background (es. per rispondere a un messaggio su WhatsApp) // Chiudiamo i rubinetti per non sprecare risorse e prevenire crash - context.read().stopListening(); - context.read().stopListening(); + _stopListeners(); debugPrint('App in background: Stream sospesi.'); }, onResume: () { // L'utente è tornato sull'app! // Riappriamo i rubinetti, Supabase ricreerà una connessione fresca - context.read().startListening(); - context.read().startListening(); + _startListeners(); debugPrint('App in foreground: Stream riattivati.'); }, ); // Facciamo partire gli stream la primissima volta che la schermata si carica + _startListeners(); + } + + void _stopListeners() { + context.read().stopListening(); + context.read().stopListening(); + context.read().stopListening(); + } + + void _startListeners() { context.read().startListening(); context.read().startListening(); + context.read().startListening(); } @override @@ -66,87 +76,70 @@ class _HomeScreenState extends State { @override Widget build(BuildContext context) { final theme = Theme.of(context); - final sessionCubit = GetIt.I.get(); - return MultiBlocProvider( - providers: [ - BlocProvider( - create: (context) => DashboardStoreOperationListCubit( - companyId: sessionCubit.state.company?.id, - storeId: sessionCubit.state.currentStore?.id, - ), - ), - BlocProvider( - create: (context) => DashboardTaskListCubit( - companyId: sessionCubit.state.company?.id, - staffId: sessionCubit.state.currentStaffMember?.id, - ), - ), - ], - child: Scaffold( - backgroundColor: theme.colorScheme.surface, - body: SafeArea( - child: Column( - children: [ - // ========================================== - // 1. HEADER FISSO (Non scrolla mai) - // ========================================== - Container( - padding: const EdgeInsets.all(24.0), - // Un leggero colore di sfondo aiuta a staccare l'header quando il contenuto ci passa sotto - color: theme.colorScheme.surface, - child: _buildHeader(context, theme), - ), + return Scaffold( + backgroundColor: theme.colorScheme.surface, + body: SafeArea( + child: Column( + children: [ + // ========================================== + // 1. HEADER FISSO (Non scrolla mai) + // ========================================== + Container( + padding: const EdgeInsets.all(24.0), + // Un leggero colore di sfondo aiuta a staccare l'header quando il contenuto ci passa sotto + color: theme.colorScheme.surface, + child: _buildHeader(context, theme), + ), - // ========================================== - // 2. CORPO DELLA DASHBOARD (Scrollabile) - // ========================================== - Expanded( - child: CustomScrollView( - slivers: [ - // --- QUICK ACTIONS: AZIONI RAPIDE --- - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 24.0), - child: _buildQuickActions(context), - ), - ), - - const SliverToBoxAdapter(child: SizedBox(height: 32)), - - // --- I WIDGET DELLA DASHBOARD (Responsive Grid) --- - SliverPadding( + // ========================================== + // 2. CORPO DELLA DASHBOARD (Scrollabile) + // ========================================== + Expanded( + child: CustomScrollView( + slivers: [ + // --- QUICK ACTIONS: AZIONI RAPIDE --- + SliverToBoxAdapter( + child: Padding( padding: const EdgeInsets.symmetric(horizontal: 24.0), - sliver: SliverGrid( - gridDelegate: - const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 500, - mainAxisSpacing: 16, - crossAxisSpacing: 16, - childAspectRatio: 1.3, - ), - delegate: SliverChildListDelegate([ - DashboardStoreOperationListCard(), - LatestStoreTicketsCard(), - _buildDashboardWidget( - title: context.l10n.homeExpiringContracts, - icon: Icons.assignment_late_outlined, - color: Colors.orange, - context: context, - ), - DashboardNotesWidget(), - DashboardTasksCard(), - ]), - ), + child: _buildQuickActions(context), ), + ), - // Spazio finale per non far attaccare l'ultima card al fondo - const SliverToBoxAdapter(child: SizedBox(height: 40)), - ], - ), + const SliverToBoxAdapter(child: SizedBox(height: 32)), + + // --- I WIDGET DELLA DASHBOARD (Responsive Grid) --- + SliverPadding( + padding: const EdgeInsets.symmetric(horizontal: 24.0), + sliver: SliverGrid( + gridDelegate: + const SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 500, + mainAxisSpacing: 16, + crossAxisSpacing: 16, + childAspectRatio: 1.3, + ), + delegate: SliverChildListDelegate([ + DashboardStoreOperationListCard(), + DashboardStoreTicketListCard(), + _buildDashboardWidget( + title: context.l10n.homeExpiringContracts, + icon: Icons.assignment_late_outlined, + color: Colors.orange, + context: context, + ), + DashboardNotesWidget(), + DashboardTasksCard(), + ]), + ), + ), + + // Spazio finale per non far attaccare l'ultima card al fondo + const SliverToBoxAdapter(child: SizedBox(height: 40)), + ], ), - ], - ), + ), + ], ), ), ); diff --git a/lib/features/tickets/data/ticket_repository.dart b/lib/features/tickets/data/ticket_repository.dart index 4af2000..46b6240 100644 --- a/lib/features/tickets/data/ticket_repository.dart +++ b/lib/features/tickets/data/ticket_repository.dart @@ -75,7 +75,9 @@ class TicketRepository { } // --- RECUPERO PAGINATO CON FILTRI E JOIN DEI TICKET DI TUTTA L'AZIENDA --- - Future> fetchCompanyTickets({ + Future> fetchTickets({ + required String? companyId, + String? storeId, required int offset, int limit = 50, String? searchTerm, @@ -96,7 +98,7 @@ class TicketRepository { target_model:${Tables.models}!ticket_model_id_1_fkey (*), source_model:${Tables.models}!ticket_model_id_2_fkey (*) ''') - .eq('company_id', GetIt.I.get().state.company!.id!); + .eq('company_id', companyId!); // Filtro Range Date if (dateRange != null) { @@ -105,6 +107,10 @@ class TicketRepository { .lte('created_at', dateRange.end.toIso8601String()); } + if (storeId != null) { + query = query.or('store_id.eq.$storeId,store_id.is.null'); + } + if (ticketStatusFilter != null) { query = query.eq('status', ticketStatusFilter.value); }