diff --git a/lib/core/routes/app_router.dart b/lib/core/routes/app_router.dart index 761e180..75a07f2 100644 --- a/lib/core/routes/app_router.dart +++ b/lib/core/routes/app_router.dart @@ -38,8 +38,12 @@ import 'package:flux/features/tickets/blocs/ticket_form_cubit.dart'; import 'package:flux/features/tickets/models/ticket_model.dart'; import 'package:flux/features/tickets/ui/ticket_form_screen.dart'; import 'package:flux/features/tickets/ui/ticket_list_screen.dart'; +import 'package:flux/features/tickets/ui/ticket_workspace/ticket_workspace_screen.dart'; +import 'package:flux/features/tracking/blocs/tracking_cubit.dart'; +import 'package:flux/features/tracking/models/tracking_model.dart'; import 'package:get_it/get_it.dart'; import 'package:go_router/go_router.dart'; +import 'package:supabase_flutter/supabase_flutter.dart'; class AppRouter { static GoRouter createRouter(SessionCubit sessionCubit) { @@ -221,6 +225,12 @@ class AppRouter { } else { realTicketId = pathId; } + if (realTicketId != null) { + context.read().loadTrackings( + realTicketId, + TrackingParentType.ticket, + ); + } context.read().loadCustomers(); context.read().loadModels(); context.read().loadBrands(); @@ -249,6 +259,36 @@ class AppRouter { ); }, ), + GoRoute( + path: '/tickets/workspace/:id', + name: Routes.ticketWorkspace, + builder: (context, state) { + // 1. Recuperiamo il Cubit vivo dall'extra + final formCubit = state.extra as TicketFormCubit?; + + // 2. Controllo di sicurezza (fondamentale per Flutter Web) + if (formCubit != null) { + return BlocProvider.value( + value: formCubit, + child: const TicketWorkspaceScreen(), + ); + } else { + // SCENARIO REFRESH WEB: + // Se l'utente preme F5 del browser mentre è nel banco da lavoro, + // l'extra viene distrutto e diventa null. + // In questo caso, gli diciamo elegantemente che la sessione è persa + // e lo invitiamo a tornare indietro, oppure restituisci direttamente + // un blocco di redirect! + return const Scaffold( + body: Center( + child: Text( + 'Sessione di lavoro scaduta. Torna alla lista e riapri il ticket.', + ), + ), + ); + } + }, + ), GoRoute( path: '/upload-success', name: Routes.uploadSuccess, diff --git a/lib/core/routes/routes.dart b/lib/core/routes/routes.dart index d78d7ed..89eff79 100644 --- a/lib/core/routes/routes.dart +++ b/lib/core/routes/routes.dart @@ -19,4 +19,5 @@ class Routes { static const String uploadSuccess = 'upload-success'; static const String customerForm = 'customer-form'; static const String upload = 'upload'; + static const String ticketWorkspace = 'ticket-workspace'; } diff --git a/lib/features/tickets/blocs/ticket_form_cubit.dart b/lib/features/tickets/blocs/ticket_form_cubit.dart index c0977bc..f147935 100644 --- a/lib/features/tickets/blocs/ticket_form_cubit.dart +++ b/lib/features/tickets/blocs/ticket_form_cubit.dart @@ -318,4 +318,49 @@ class TicketFormCubit extends Cubit { ); } } + + Future completeTicket({ + required TicketResult result, + required double finalPrice, + }) async { + final currentTicket = state.ticket; + + if (currentTicket.id == null || currentTicket.id!.isEmpty) return; + + // 1. Aggiorniamo il ticket con il nuovo status, l'esito e il prezzo! + final updatedTicket = currentTicket.copyWith( + ticketStatus: TicketStatus.ready, + ticketResult: result, + customerPrice: finalPrice, + ); + + try { + await _repository.updateTicket(updatedTicket); + + // 2. Timeline personalizzata in base all'esito + final esitoTesto = result == TicketResult.success + ? "Riparato con successo" + : "Non riparabile/Preventivo rifiutato"; + + await GetIt.I.get().logQuickEvent( + companyId: currentTicket.companyId, + message: + "Lavorazione completata. Esito: $esitoTesto. Il dispositivo è pronto per il ritiro.", + type: TrackingType.statusChange, + parentId: currentTicket.id!, + parentType: TrackingParentType.ticket, + staffId: currentTicket.assignedToId ?? '', + isInternal: false, + ); + + emit(state.copyWith(ticket: updatedTicket)); + } catch (e) { + emit( + state.copyWith( + status: TicketFormStatus.failure, + errorMessage: 'Errore durante la chiusura: $e', + ), + ); + } + } } diff --git a/lib/features/tickets/ui/ticket_form_screen.dart b/lib/features/tickets/ui/ticket_form_screen.dart index d279721..2325781 100644 --- a/lib/features/tickets/ui/ticket_form_screen.dart +++ b/lib/features/tickets/ui/ticket_form_screen.dart @@ -3,6 +3,7 @@ import 'dart:io' show Platform; 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/widgets/shared_forms/customer_section.dart'; import 'package:flux/core/widgets/shared_forms/model_section.dart'; import 'package:flux/core/widgets/shared_forms/shared_files_section.dart'; @@ -22,6 +23,7 @@ import 'package:flux/features/tickets/utils/ticket_pdf_service.dart'; import 'package:flux/features/tracking/blocs/tracking_cubit.dart'; import 'package:flux/features/tracking/models/tracking_model.dart'; import 'package:get_it/get_it.dart'; +import 'package:go_router/go_router.dart'; import 'package:printing/printing.dart'; class TicketFormScreen extends StatefulWidget { @@ -278,6 +280,19 @@ class _TicketFormScreenState extends State { ); } + void _navigateToWorkspace(String ticketId) async { + final formCubit = context.read(); + final trackingCubit = context.read(); + _flushControllersToCubit(); + await context.pushNamed( + Routes.ticketWorkspace, // Assicurati di aver definito questo nome! + pathParameters: {'id': ticketId}, + extra: formCubit, // Passiamo l'intero Cubit come extra! + ); + if (!context.mounted) return; + trackingCubit.loadTrackings(ticketId, TrackingParentType.ticket); + } + @override Widget build(BuildContext context) { final theme = Theme.of(context); @@ -345,24 +360,11 @@ class _TicketFormScreenState extends State { context, ); if (takenBy == null || !context.mounted) return; - final formCubit = context.read(); - _flushControllersToCubit(); context.read().takeInCharge( staffId: takenBy.id!, staffName: takenBy.name, ); - if (!context.mounted) return; - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => BlocProvider.value( - value: - formCubit, // Magia! La nuova schermata userà lo stesso Cubit! - child: - const TicketWorkspaceScreen(), // Non passiamo più il ticket - ), - ), - ); + _navigateToWorkspace(ticket.id!); }, icon: const Icon(Icons.play_arrow, color: Colors.white), label: const Text( @@ -380,23 +382,7 @@ class _TicketFormScreenState extends State { vertical: 8.0, ), child: FilledButton.icon( - onPressed: () { - final formCubit = context.read(); - // Naviga direttamente alla schermata di lavorazione - _flushControllersToCubit(); - if (!context.mounted) return; - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => BlocProvider.value( - value: - formCubit, // Magia! La nuova schermata userà lo stesso Cubit! - child: - const TicketWorkspaceScreen(), // Non passiamo più il ticket - ), - ), - ); - }, + onPressed: () => _navigateToWorkspace(ticket.id!), icon: const Icon(Icons.handyman), label: const Text('Vai a Lavorazione'), ), @@ -971,38 +957,7 @@ class _TicketFormScreenState extends State { title: 'Timeline & Note', icon: Icons.history, themeColor: Colors.blueGrey, - children: [ - BlocProvider( - create: (context) => TrackingCubit( - parentId: ticket.id!, - parentType: TrackingParentType.ticket, - ), - child: BlocBuilder( - builder: (context, state) { - if (state.isLoading && state.logs.isEmpty) { - return const Center(child: CircularProgressIndicator()); - } - - return TicketTimelineSection( - logs: state.logs, - onAddNote: (message, isInternal) { - // Recupera l'ID dello staff loggato dal tuo auth state - // final currentStaffId = ... - context.read().addManualNote( - message, - isInternal, - staffId: GetIt.I - .get() - .state - .currentStaffMember - ?.id, - ); - }, - ); - }, - ), - ), - ], + children: [TicketTimelineSection(ticketId: ticket.id!)], ); } diff --git a/lib/features/tickets/ui/ticket_list_screen.dart b/lib/features/tickets/ui/ticket_list_screen.dart index 6eed79f..efffb34 100644 --- a/lib/features/tickets/ui/ticket_list_screen.dart +++ b/lib/features/tickets/ui/ticket_list_screen.dart @@ -155,11 +155,13 @@ class _TicketListScreenState extends State { onPressed: () async { StaffMemberModel? createdBy = await getStaffMember(context); if (createdBy == null || !context.mounted) return; - context.pushNamed( + await context.pushNamed( Routes.ticketForm, pathParameters: {'id': 'new'}, extra: (createdBy: createdBy, ticket: null), ); + if (!context.mounted) return; + context.read().fetchTickets(reset: true); }, ), ); diff --git a/lib/features/tickets/ui/ticket_timeline_section.dart b/lib/features/tickets/ui/ticket_timeline_section.dart index 5aa6fa9..f46da60 100644 --- a/lib/features/tickets/ui/ticket_timeline_section.dart +++ b/lib/features/tickets/ui/ticket_timeline_section.dart @@ -1,15 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flux/features/tracking/blocs/tracking_cubit.dart'; import 'package:flux/features/tracking/models/tracking_model.dart'; class TicketTimelineSection extends StatefulWidget { - final List logs; - final void Function(String message, bool isInternal) onAddNote; + final String ticketId; - const TicketTimelineSection({ - super.key, - required this.logs, - required this.onAddNote, - }); + const TicketTimelineSection({super.key, required this.ticketId}); @override State createState() => _TicketTimelineSectionState(); @@ -28,7 +25,12 @@ class _TicketTimelineSectionState extends State { void _submitNote() { final text = _textController.text.trim(); if (text.isNotEmpty) { - widget.onAddNote(text, _isInternal); + context.read().addTimelineEvent( + parentId: widget.ticketId, + parentType: TrackingParentType.ticket, + message: text, + isInternal: _isInternal, + ); _textController.clear(); // Chiudiamo la tastiera se siamo su mobile FocusScope.of(context).unfocus(); @@ -124,129 +126,137 @@ class _TicketTimelineSectionState extends State { const SizedBox(height: 24), // --- TIMELINE SCROLLABILE --- - if (widget.logs.isEmpty) - const Padding( - padding: EdgeInsets.all(32.0), - child: Center( - child: Text( - 'Nessun evento registrato.', - style: TextStyle(color: Colors.grey), - ), - ), - ) - else - ConstrainedBox( - constraints: const BoxConstraints( - maxHeight: 400, - ), // Limite di altezza - child: ListView.builder( - shrinkWrap: true, - itemCount: widget.logs.length, - itemBuilder: (context, index) { - final log = widget.logs[index]; - final isLast = index == widget.logs.length - 1; + BlocBuilder( + builder: (context, state) { + if (state.isLoading) { + return const Center(child: CircularProgressIndicator()); + } + if (state.logs.isEmpty) { + return const Padding( + padding: EdgeInsets.all(32.0), + child: Center( + child: Text( + 'Nessun evento registrato.', + style: TextStyle(color: Colors.grey), + ), + ), + ); + } + return ConstrainedBox( + constraints: const BoxConstraints( + maxHeight: 400, + ), // Limite di altezza + child: ListView.builder( + shrinkWrap: true, + itemCount: state.logs.length, + itemBuilder: (context, index) { + final log = state.logs[index]; + final isLast = index == state.logs.length - 1; - return IntrinsicHeight( - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // --- LINEA E PALLINO --- - SizedBox( - width: 30, - child: Column( - children: [ - Container( - margin: const EdgeInsets.only(top: 4), - width: 14, - height: 14, - decoration: BoxDecoration( - color: _getEventColor(log.eventType), - shape: BoxShape.circle, - border: Border.all( - color: theme.scaffoldBackgroundColor, - width: 2, - ), - ), - ), - if (!isLast) - Expanded( - child: Container( - width: 2, - color: theme.dividerColor, - ), - ), - ], - ), - ), - - // --- CONTENUTO EVENTO --- - Expanded( - child: Padding( - padding: const EdgeInsets.only(bottom: 24.0), + return IntrinsicHeight( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // --- LINEA E PALLINO --- + SizedBox( + width: 30, child: Column( - crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - children: [ - Text( - log.staffName ?? 'Sistema', - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 13, - ), + Container( + margin: const EdgeInsets.only(top: 4), + width: 14, + height: 14, + decoration: BoxDecoration( + color: _getEventColor(log.eventType), + shape: BoxShape.circle, + border: Border.all( + color: theme.scaffoldBackgroundColor, + width: 2, ), - const SizedBox(width: 8), - Text( - _formatDate(log.createdAt), - style: theme.textTheme.bodySmall?.copyWith( - color: Colors.grey, - ), + ), + ), + if (!isLast) + Expanded( + child: Container( + width: 2, + color: theme.dividerColor, ), - if (!log.isInternal) ...[ - const Spacer(), - Container( - padding: const EdgeInsets.symmetric( - horizontal: 6, - vertical: 2, - ), - decoration: BoxDecoration( - color: Colors.green.withValues( - alpha: 0.1, - ), - borderRadius: BorderRadius.circular(4), - border: Border.all( - color: Colors.green.withValues( - alpha: 0.3, - ), - ), - ), - child: const Text( - "PUBBLICO", - style: TextStyle( - color: Colors.green, - fontSize: 9, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - ], - ), - const SizedBox(height: 6), - Text( - log.message, - style: const TextStyle(fontSize: 14), - ), + ), ], ), ), - ), - ], - ), - ); - }, - ), - ), + + // --- CONTENUTO EVENTO --- + Expanded( + child: Padding( + padding: const EdgeInsets.only(bottom: 24.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Text( + log.staffName ?? 'Sistema', + style: const TextStyle( + fontWeight: FontWeight.bold, + fontSize: 13, + ), + ), + const SizedBox(width: 8), + Text( + _formatDate(log.createdAt), + style: theme.textTheme.bodySmall + ?.copyWith(color: Colors.grey), + ), + if (!log.isInternal) ...[ + const Spacer(), + Container( + padding: const EdgeInsets.symmetric( + horizontal: 6, + vertical: 2, + ), + decoration: BoxDecoration( + color: Colors.green.withValues( + alpha: 0.1, + ), + borderRadius: BorderRadius.circular( + 4, + ), + border: Border.all( + color: Colors.green.withValues( + alpha: 0.3, + ), + ), + ), + child: const Text( + "PUBBLICO", + style: TextStyle( + color: Colors.green, + fontSize: 9, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ], + ), + const SizedBox(height: 6), + Text( + log.message, + style: const TextStyle(fontSize: 14), + ), + ], + ), + ), + ), + ], + ), + ); + }, + ), + ); + }, + ), ], ); } diff --git a/lib/features/tickets/ui/ticket_workspace/completion_ticket_dialog.dart b/lib/features/tickets/ui/ticket_workspace/completion_ticket_dialog.dart new file mode 100644 index 0000000..c1ac27a --- /dev/null +++ b/lib/features/tickets/ui/ticket_workspace/completion_ticket_dialog.dart @@ -0,0 +1,154 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flux/features/tickets/models/ticket_model.dart'; + +Future<({TicketResult result, double finalPrice})?> showCompletionTicketDialog( + BuildContext context, { + required double calculatedTotal, +}) async { + return showDialog<({TicketResult result, double finalPrice})>( + context: context, + barrierDismissible: false, // Obbliga l'utente a premere un bottone + builder: (context) => CompletionTicketDialog(initialTotal: calculatedTotal), + ); +} + +class CompletionTicketDialog extends StatefulWidget { + final double initialTotal; + + const CompletionTicketDialog({super.key, required this.initialTotal}); + + @override + State createState() => _CompletionTicketDialogState(); +} + +class _CompletionTicketDialogState extends State { + late final TextEditingController _priceCtrl; + TicketResult _selectedResult = TicketResult.success; // Default: Riparato! + + @override + void initState() { + super.initState(); + // Inizializziamo il campo testuale con il totale calcolato + _priceCtrl = TextEditingController( + text: widget.initialTotal.toStringAsFixed(2), + ); + } + + @override + void dispose() { + _priceCtrl.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + + return AlertDialog( + title: const Row( + children: [ + Icon(Icons.check_circle_outline, color: Colors.green), + SizedBox(width: 8), + Text('Completa Lavorazione'), + ], + ), + content: SizedBox( + width: double.maxFinite, // Per allargare bene la modale + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + 'Specifica l\'esito della lavorazione e il totale da incassare.', + ), + const SizedBox(height: 24), + + // --- ESITO LAVORAZIONE (Choice Chips) --- + const Text('Esito:', style: TextStyle(fontWeight: FontWeight.bold)), + const SizedBox(height: 8), + Row( + children: [ + Expanded( + child: ChoiceChip( + label: const Center(child: Text('Riparato')), + selectedColor: Colors.green.withValues(alpha: 0.2), + selected: _selectedResult == TicketResult.success, + onSelected: (selected) { + if (selected) { + setState(() => _selectedResult = TicketResult.success); + } + }, + ), + ), + const SizedBox(width: 8), + Expanded( + child: ChoiceChip( + label: const Center(child: Text('Non Riparato')), + selectedColor: Colors.red.withValues(alpha: 0.2), + selected: _selectedResult == TicketResult.failure, + onSelected: (selected) { + if (selected) { + setState(() => _selectedResult = TicketResult.failure); + } + }, + ), + ), + ], + ), + const SizedBox(height: 24), + + // --- PREZZO FINALE --- + const Text( + 'Totale da pagare al ritiro:', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(height: 8), + TextFormField( + controller: _priceCtrl, + keyboardType: const TextInputType.numberWithOptions( + decimal: true, + ), + inputFormatters: [ + FilteringTextInputFormatter.allow( + RegExp(r'[0-9.,]'), + ), // Solo numeri, punti e virgole + ], + style: const TextStyle(fontSize: 24, fontWeight: FontWeight.bold), + decoration: InputDecoration( + prefixIcon: const Icon(Icons.euro), + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12), + ), + filled: true, + fillColor: theme.colorScheme.surfaceContainerHighest.withValues( + alpha: 0.3, + ), + ), + ), + ], + ), + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('Annulla'), + ), + FilledButton.icon( + style: FilledButton.styleFrom(backgroundColor: Colors.green.shade600), + onPressed: () { + // Conversione sicura: cambiamo un'eventuale virgola italiana in punto + final safePriceText = _priceCtrl.text.replaceAll(',', '.'); + final finalPrice = double.tryParse(safePriceText) ?? 0.0; + + Navigator.of( + context, + ).pop((result: _selectedResult, finalPrice: finalPrice)); + }, + icon: const Icon(Icons.save), + label: const Text('Conferma Completamento'), + ), + ], + ); + } +} diff --git a/lib/features/tickets/ui/ticket_workspace/ticket_workspace_screen.dart b/lib/features/tickets/ui/ticket_workspace/ticket_workspace_screen.dart index 245f6dd..0e8d16a 100644 --- a/lib/features/tickets/ui/ticket_workspace/ticket_workspace_screen.dart +++ b/lib/features/tickets/ui/ticket_workspace/ticket_workspace_screen.dart @@ -1,9 +1,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flux/core/routes/routes.dart'; import 'package:flux/features/tickets/blocs/ticket_form_cubit.dart'; import 'package:flux/features/tickets/blocs/ticket_form_state.dart'; +import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart'; import 'package:flux/features/tickets/models/ticket_model.dart'; +import 'package:flux/features/tickets/ui/ticket_workspace/completion_ticket_dialog.dart'; import 'package:flux/features/tickets/ui/ticket_workspace/pause_ticket_dialog.dart'; +import 'package:go_router/go_router.dart'; class TicketWorkspaceScreen extends StatelessWidget { const TicketWorkspaceScreen({super.key}); @@ -285,8 +289,30 @@ class TicketWorkspaceScreen extends StatelessWidget { padding: const EdgeInsets.symmetric(vertical: 16), backgroundColor: Colors.green.shade600, ), - onPressed: () { - // TODO: Logica Completa Riparazione + onPressed: () async { + // 1. Apriamo la nuova modale avanzata e aspettiamo il Record. + // (Per ora passiamo 0.0, poi lo sostituiremo con la somma dei costi) + final completionData = await showCompletionTicketDialog( + context, + calculatedTotal: 0.0, + ); + + // 2. Se l'utente ha premuto "Conferma" e non "Annulla"... + if (completionData != null && context.mounted) { + // 3. Lanciamo l'azione nel Cubit + await context.read().completeTicket( + result: completionData.result, + finalPrice: completionData.finalPrice, + ); + + if (context.mounted) { + // 4. Avvisiamo il "Vigile Urbano" di ricaricare la lista + context.read().fetchTickets(reset: true); + + // 5. Teletrasporto alla Base + context.goNamed(Routes.home); + } + } }, icon: const Icon(Icons.check_circle_outline), label: const Text( diff --git a/lib/features/tracking/blocs/tracking_cubit.dart b/lib/features/tracking/blocs/tracking_cubit.dart index 1758711..1139ed3 100644 --- a/lib/features/tracking/blocs/tracking_cubit.dart +++ b/lib/features/tracking/blocs/tracking_cubit.dart @@ -14,17 +14,15 @@ class TrackingState { class TrackingCubit extends Cubit { final TrackingRepository _repo = GetIt.I.get(); - final String parentId; - final TrackingParentType parentType; final String companyId = GetIt.I.get().state.company!.id!; - TrackingCubit({required this.parentId, required this.parentType}) - : super(TrackingState()) { - loadTrackings(); - } + TrackingCubit() : super(TrackingState()); - Future loadTrackings() async { - emit(TrackingState(isLoading: true, logs: state.logs)); + Future loadTrackings( + String parentId, + TrackingParentType parentType, + ) async { + emit(TrackingState(isLoading: true, logs: [])); final trackings = await _repo.getTrackingsByParent( parentId: parentId, parentType: parentType, @@ -32,9 +30,11 @@ class TrackingCubit extends Cubit { emit(TrackingState(isLoading: false, logs: trackings)); } - Future addManualNote( - String message, - bool isInternal, { + Future addTimelineEvent({ + required String parentId, + required TrackingParentType parentType, + required String message, + required bool isInternal, String? staffId, }) async { // Aggiungiamo un feedback visivo immediato (Optimistic UI) se vogliamo, @@ -49,6 +49,6 @@ class TrackingCubit extends Cubit { isInternal: isInternal, ); // Ricarichiamo la lista fresca dal server - await loadTrackings(); + await loadTrackings(parentId, parentType); } } diff --git a/lib/features/tracking/data/tracking_repository.dart b/lib/features/tracking/data/tracking_repository.dart index 33160f1..424fb39 100644 --- a/lib/features/tracking/data/tracking_repository.dart +++ b/lib/features/tracking/data/tracking_repository.dart @@ -18,10 +18,7 @@ class TrackingRepository { .select('*, staff_member(name)') .eq('parent_id', parentId) .eq('parent_type', parentType.name) - .order( - 'created_at', - ascending: true, - ); // ascending: true per avere la timeline dall'alto (vecchi) al basso (nuovi) + .order('created_at', ascending: false); return response.map((map) => TrackingModel.fromMap(map)).toList(); } diff --git a/lib/main.dart b/lib/main.dart index 4ca5f53..d34325f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -13,6 +13,7 @@ import 'package:flux/features/settings/blocs/settings_cubit.dart'; import 'package:flux/features/settings/document_sequence/data/document_sequence_repository.dart'; import 'package:flux/features/tickets/blocs/ticket_list_cubit.dart'; import 'package:flux/features/tickets/data/ticket_repository.dart'; +import 'package:flux/features/tracking/blocs/tracking_cubit.dart'; import 'package:flux/features/tracking/data/tracking_repository.dart'; import 'package:flux/l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; @@ -70,6 +71,7 @@ void main() async { BlocProvider(create: (_) => SettingsCubit()), BlocProvider(create: (_) => TicketListCubit()), BlocProvider(create: (_) => OperationListCubit()), + BlocProvider(create: (_) => TrackingCubit()), ], child: const FluxApp(), ),