From 1d45912fc78e7f2e0bb654c593dfac50ef968120 Mon Sep 17 00:00:00 2001 From: Mark M2 Macbook Date: Wed, 6 May 2026 00:08:25 +0200 Subject: [PATCH] feat: setup completo architettura ticket, UI e cubit --- lib/core/routes/app_router.dart | 9 + .../ui/latest_store_operations_card.dart | 237 +++++++++--------- lib/features/home/ui/home_screen.dart | 108 ++++---- .../tickets/data/ticket_repository.dart | 4 +- 4 files changed, 189 insertions(+), 169 deletions(-) diff --git a/lib/core/routes/app_router.dart b/lib/core/routes/app_router.dart index 1ac0980..6f9ccac 100644 --- a/lib/core/routes/app_router.dart +++ b/lib/core/routes/app_router.dart @@ -29,6 +29,8 @@ import 'package:flux/features/operations/models/operation_model.dart'; import 'package:flux/features/operations/ui/operation_form_screen.dart'; import 'package:flux/features/operations/ui/operation_mobile_upload_screen.dart'; import 'package:flux/features/operations/ui/operations_screen.dart'; +import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart'; +import 'package:flux/features/tickets/ui/ticket_list_screen.dart'; import 'package:get_it/get_it.dart'; import 'package:go_router/go_router.dart'; @@ -146,6 +148,13 @@ class AppRouter { builder: (context, state) => const CustomersContent(), // O come si chiama il tuo widget della lista! ), + GoRoute( + path: '/tickets', + builder: (context, state) => BlocProvider( + create: (context) => TicketListCubit(), + child: const TicketListScreen(), + ), + ), ], ), diff --git a/lib/features/home/latest_store_operations/ui/latest_store_operations_card.dart b/lib/features/home/latest_store_operations/ui/latest_store_operations_card.dart index 1380b0a..2832680 100644 --- a/lib/features/home/latest_store_operations/ui/latest_store_operations_card.dart +++ b/lib/features/home/latest_store_operations/ui/latest_store_operations_card.dart @@ -47,30 +47,30 @@ class _LatestOperationsCardContent extends StatelessWidget { borderRadius: BorderRadius.circular(16), side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)), ), - 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: InkWell( + onTap: () => context.push('/operations'), + 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, + ), ), - child: const Icon( - Icons.design_services_outlined, - color: color, - size: 20, - ), - ), - const SizedBox(width: 12), - Expanded( - child: TextButton( - onPressed: () => context.push('/operations'), + const SizedBox(width: 12), + Expanded( child: Text( context.l10n.homeLatestOperations, style: TextStyle( @@ -82,106 +82,111 @@ class _LatestOperationsCardContent extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), - ), - ], - ), - const SizedBox(height: 12), + ], + ), + const SizedBox(height: 12), - // --- CORPO DELLA CARD (LA LISTA REAL-TIME) --- - Expanded( - child: - BlocBuilder< - LatestStoreOperationsBloc, - LatestStoreOperationsState - >( - builder: (context, state) { - if (state.status == LatestStoreOperationsStatus.loading || - state.status == LatestStoreOperationsStatus.initial) { - return const Center(child: CircularProgressIndicator()); - } + // --- CORPO DELLA CARD (LA LISTA REAL-TIME) --- + Expanded( + child: + BlocBuilder< + LatestStoreOperationsBloc, + LatestStoreOperationsState + >( + builder: (context, state) { + if (state.status == + LatestStoreOperationsStatus.loading || + state.status == + LatestStoreOperationsStatus.initial) { + return const Center( + child: CircularProgressIndicator(), + ); + } - if (state.status == LatestStoreOperationsStatus.failure) { - return Center( - child: Text( - "Errore di caricamento", - style: TextStyle(color: theme.colorScheme.error), - ), - ); - } - - if (state.operations.isEmpty) { - return Center( - child: Text( - "Nessun servizio recente.", - style: TextStyle( - color: context.secondaryText, - fontStyle: FontStyle.italic, + if (state.status == + LatestStoreOperationsStatus.failure) { + return Center( + child: Text( + "Errore di caricamento", + style: TextStyle(color: theme.colorScheme.error), ), - ), - ); - } + ); + } - return ListView.separated( - itemCount: state.operations.length, - separatorBuilder: (context, index) => Divider( - height: 1, - color: theme.dividerColor.withValues(alpha: 0.3), - ), - itemBuilder: (context, index) { - final operation = state.operations[index]; - return InkWell( - onTap: () => context.push( - '/operation-form', - extra: operation, - ), - child: Padding( - padding: const EdgeInsets.symmetric( - vertical: 8.0, - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - flex: 5, - child: Text( - operation.customerDisplayName ?? - 'Cliente sconosciuto', - style: TextStyle( - fontWeight: FontWeight.w700, - color: context.primaryText, - ), - ), - ), - Expanded( - flex: 5, - child: Text( - operation.reference, - style: TextStyle( - fontWeight: FontWeight.w600, - color: context.primaryText, - ), - maxLines: 1, - overflow: TextOverflow.ellipsis, - ), - ), - Text( - "${operation.createdAt?.day}/${operation.createdAt?.month}", - style: TextStyle( - color: context.secondaryText, - fontSize: 12, - ), - ), - ], + if (state.operations.isEmpty) { + return Center( + child: Text( + "Nessun servizio recente.", + style: TextStyle( + color: context.secondaryText, + fontStyle: FontStyle.italic, ), ), ); - }, - ); - }, - ), - ), - ], + } + + return ListView.separated( + itemCount: state.operations.length, + separatorBuilder: (context, index) => Divider( + height: 1, + color: theme.dividerColor.withValues(alpha: 0.3), + ), + itemBuilder: (context, index) { + final operation = state.operations[index]; + return InkWell( + onTap: () => context.push( + '/operation-form', + extra: operation, + ), + child: Padding( + padding: const EdgeInsets.symmetric( + vertical: 8.0, + ), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 5, + child: Text( + operation.customerDisplayName ?? + 'Cliente sconosciuto', + style: TextStyle( + fontWeight: FontWeight.w700, + color: context.primaryText, + ), + ), + ), + Expanded( + flex: 5, + child: Text( + operation.reference, + style: TextStyle( + fontWeight: FontWeight.w600, + color: context.primaryText, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + Text( + "${operation.createdAt?.day}/${operation.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 40bef0a..9029872 100644 --- a/lib/features/home/ui/home_screen.dart +++ b/lib/features/home/ui/home_screen.dart @@ -77,12 +77,13 @@ class HomeScreen extends StatelessWidget { context: context, ), LatestStoreOperationsCard(), - _buildDashboardWidget( title: context.l10n.homeLatestOperationTickets, icon: Icons.support_agent_outlined, color: Colors.purple, context: context, + onTap: () => + context.push('/tickets'), // <-- Aggiunto! ), ]), ), @@ -194,8 +195,8 @@ class HomeScreen extends StatelessWidget { label: context.l10n.homeNewOperationTicket, color: Colors.redAccent, onTap: () { - // TODO: Quando avrai la rotta per la nuova assistenza - // context.push('/assistance-form'); + // Andiamo alla lista! (Da lì poi aggiungeremo il tasto "+" per il form) + context.push('/tickets'); }, ), const SizedBox(width: 12), @@ -226,68 +227,73 @@ class HomeScreen extends StatelessWidget { required String title, required IconData icon, required Color color, + VoidCallback? onTap, }) { final theme = Theme.of(context); return Card( elevation: 0, + clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(16), side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)), ), - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: color.withValues(alpha: 0.1), - borderRadius: BorderRadius.circular(8), - ), - child: Icon(icon, color: color, size: 20), - ), - const SizedBox(width: 12), - Expanded( - child: Text( - title, - style: TextStyle( - fontWeight: FontWeight.bold, - fontSize: 16, - color: context.primaryText, + child: InkWell( + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: color.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8), ), - maxLines: 1, - overflow: TextOverflow.ellipsis, + child: Icon(icon, color: color, size: 20), ), - ), - IconButton( - icon: Icon( - Icons.more_vert, - size: 20, - color: context.secondaryText, + const SizedBox(width: 12), + Expanded( + child: Text( + title, + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 16, + color: context.primaryText, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + IconButton( + icon: Icon( + Icons.more_vert, + size: 20, + color: context.secondaryText, + ), + onPressed: () {}, + padding: EdgeInsets.zero, + constraints: const BoxConstraints(), + ), + ], + ), + const Spacer(), + Center( + child: Text( + context.l10n.commonComingSoon, + style: TextStyle( + color: context.secondaryText.withValues(alpha: 0.7), + fontStyle: FontStyle.italic, + fontSize: 13, ), - onPressed: () {}, - padding: EdgeInsets.zero, - constraints: const BoxConstraints(), - ), - ], - ), - const Spacer(), - Center( - child: Text( - context.l10n.commonComingSoon, - style: TextStyle( - color: context.secondaryText.withValues(alpha: 0.7), - fontStyle: FontStyle.italic, - fontSize: 13, ), ), - ), - const Spacer(), - ], + const Spacer(), + ], + ), ), ), ); diff --git a/lib/features/tickets/data/ticket_repository.dart b/lib/features/tickets/data/ticket_repository.dart index 4adc9a3..df30905 100644 --- a/lib/features/tickets/data/ticket_repository.dart +++ b/lib/features/tickets/data/ticket_repository.dart @@ -27,7 +27,7 @@ class TicketRepository { .select(''' *, customer (*), - staff (*), + staff_member (*), target_model:model!ticket_model_id_1_fkey (*), source_model:model!ticket_model_id_2_fkey (*) ''') @@ -83,7 +83,7 @@ class TicketRepository { .select(''' *, customer (*), - staff (*), + staff_member (*), target_model:model!ticket_model_id_1_fkey (*), source_model:model!ticket_model_id_2_fkey (*) ''')