feat: setup completo architettura ticket, UI e cubit

This commit is contained in:
2026-05-06 00:08:25 +02:00
parent 0c8b9ae3ec
commit 1d45912fc7
4 changed files with 189 additions and 169 deletions

View File

@@ -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_form_screen.dart';
import 'package:flux/features/operations/ui/operation_mobile_upload_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/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:get_it/get_it.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
@@ -146,6 +148,13 @@ class AppRouter {
builder: (context, state) => builder: (context, state) =>
const CustomersContent(), // O come si chiama il tuo widget della lista! const CustomersContent(), // O come si chiama il tuo widget della lista!
), ),
GoRoute(
path: '/tickets',
builder: (context, state) => BlocProvider(
create: (context) => TicketListCubit(),
child: const TicketListScreen(),
),
),
], ],
), ),

View File

@@ -47,30 +47,30 @@ class _LatestOperationsCardContent extends StatelessWidget {
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)), side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)),
), ),
child: Padding( child: InkWell(
padding: const EdgeInsets.all(16.0), onTap: () => context.push('/operations'),
child: Column( child: Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.all(16.0),
children: [ child: Column(
// --- HEADER DELLA CARD --- crossAxisAlignment: CrossAxisAlignment.start,
Row( children: [
children: [ // --- HEADER DELLA CARD ---
Container( Row(
padding: const EdgeInsets.all(8), children: [
decoration: BoxDecoration( Container(
color: color.withValues(alpha: 0.1), padding: const EdgeInsets.all(8),
borderRadius: BorderRadius.circular(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( const SizedBox(width: 12),
Icons.design_services_outlined, Expanded(
color: color,
size: 20,
),
),
const SizedBox(width: 12),
Expanded(
child: TextButton(
onPressed: () => context.push('/operations'),
child: Text( child: Text(
context.l10n.homeLatestOperations, context.l10n.homeLatestOperations,
style: TextStyle( style: TextStyle(
@@ -82,106 +82,111 @@ class _LatestOperationsCardContent extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
), ),
), ],
], ),
), const SizedBox(height: 12),
const SizedBox(height: 12),
// --- CORPO DELLA CARD (LA LISTA REAL-TIME) --- // --- CORPO DELLA CARD (LA LISTA REAL-TIME) ---
Expanded( Expanded(
child: child:
BlocBuilder< BlocBuilder<
LatestStoreOperationsBloc, LatestStoreOperationsBloc,
LatestStoreOperationsState LatestStoreOperationsState
>( >(
builder: (context, state) { builder: (context, state) {
if (state.status == LatestStoreOperationsStatus.loading || if (state.status ==
state.status == LatestStoreOperationsStatus.initial) { LatestStoreOperationsStatus.loading ||
return const Center(child: CircularProgressIndicator()); state.status ==
} LatestStoreOperationsStatus.initial) {
return const Center(
child: CircularProgressIndicator(),
);
}
if (state.status == LatestStoreOperationsStatus.failure) { if (state.status ==
return Center( LatestStoreOperationsStatus.failure) {
child: Text( return Center(
"Errore di caricamento", child: Text(
style: TextStyle(color: theme.colorScheme.error), "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,
), ),
), );
); }
}
return ListView.separated( if (state.operations.isEmpty) {
itemCount: state.operations.length, return Center(
separatorBuilder: (context, index) => Divider( child: Text(
height: 1, "Nessun servizio recente.",
color: theme.dividerColor.withValues(alpha: 0.3), style: TextStyle(
), color: context.secondaryText,
itemBuilder: (context, index) { fontStyle: FontStyle.italic,
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,
),
),
],
), ),
), ),
); );
}, }
);
}, 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,
),
),
],
),
),
);
},
);
},
),
),
],
),
), ),
), ),
); );

View File

@@ -77,12 +77,13 @@ class HomeScreen extends StatelessWidget {
context: context, context: context,
), ),
LatestStoreOperationsCard(), LatestStoreOperationsCard(),
_buildDashboardWidget( _buildDashboardWidget(
title: context.l10n.homeLatestOperationTickets, title: context.l10n.homeLatestOperationTickets,
icon: Icons.support_agent_outlined, icon: Icons.support_agent_outlined,
color: Colors.purple, color: Colors.purple,
context: context, context: context,
onTap: () =>
context.push('/tickets'), // <-- Aggiunto!
), ),
]), ]),
), ),
@@ -194,8 +195,8 @@ class HomeScreen extends StatelessWidget {
label: context.l10n.homeNewOperationTicket, label: context.l10n.homeNewOperationTicket,
color: Colors.redAccent, color: Colors.redAccent,
onTap: () { onTap: () {
// TODO: Quando avrai la rotta per la nuova assistenza // Andiamo alla lista! (Da lì poi aggiungeremo il tasto "+" per il form)
// context.push('/assistance-form'); context.push('/tickets');
}, },
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
@@ -226,68 +227,73 @@ class HomeScreen extends StatelessWidget {
required String title, required String title,
required IconData icon, required IconData icon,
required Color color, required Color color,
VoidCallback? onTap,
}) { }) {
final theme = Theme.of(context); final theme = Theme.of(context);
return Card( return Card(
elevation: 0, elevation: 0,
clipBehavior: Clip.antiAlias,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16), borderRadius: BorderRadius.circular(16),
side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)), side: BorderSide(color: theme.dividerColor.withValues(alpha: 0.5)),
), ),
child: Padding( child: InkWell(
padding: const EdgeInsets.all(16.0), onTap: onTap,
child: Column( child: Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.all(16.0),
children: [ child: Column(
Row( crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Row(
padding: const EdgeInsets.all(8), children: [
decoration: BoxDecoration( Container(
color: color.withValues(alpha: 0.1), padding: const EdgeInsets.all(8),
borderRadius: BorderRadius.circular(8), decoration: BoxDecoration(
), color: color.withValues(alpha: 0.1),
child: Icon(icon, color: color, size: 20), borderRadius: BorderRadius.circular(8),
),
const SizedBox(width: 12),
Expanded(
child: Text(
title,
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 16,
color: context.primaryText,
), ),
maxLines: 1, child: Icon(icon, color: color, size: 20),
overflow: TextOverflow.ellipsis,
), ),
), const SizedBox(width: 12),
IconButton( Expanded(
icon: Icon( child: Text(
Icons.more_vert, title,
size: 20, style: TextStyle(
color: context.secondaryText, 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(), ],
], ),
), ),
), ),
); );

View File

@@ -27,7 +27,7 @@ class TicketRepository {
.select(''' .select('''
*, *,
customer (*), customer (*),
staff (*), staff_member (*),
target_model:model!ticket_model_id_1_fkey (*), target_model:model!ticket_model_id_1_fkey (*),
source_model:model!ticket_model_id_2_fkey (*) source_model:model!ticket_model_id_2_fkey (*)
''') ''')
@@ -83,7 +83,7 @@ class TicketRepository {
.select(''' .select('''
*, *,
customer (*), customer (*),
staff (*), staff_member (*),
target_model:model!ticket_model_id_1_fkey (*), target_model:model!ticket_model_id_1_fkey (*),
source_model:model!ticket_model_id_2_fkey (*) source_model:model!ticket_model_id_2_fkey (*)
''') ''')