This commit is contained in:
2026-05-10 15:36:26 +02:00
parent 5c86483563
commit b91442872d
2 changed files with 163 additions and 1 deletions

View File

@@ -107,6 +107,7 @@ class TicketModel extends Equatable {
final TicketResult? ticketResult; final TicketResult? ticketResult;
final String? resolutionNotes; final String? resolutionNotes;
final String? customerName; final String? customerName;
final String? customerEmail;
final String? targetModelName; final String? targetModelName;
final String? sourceModelName; final String? sourceModelName;
final String? createdById; final String? createdById;
@@ -142,6 +143,7 @@ class TicketModel extends Equatable {
this.ticketResult, this.ticketResult,
this.resolutionNotes, this.resolutionNotes,
this.customerName, this.customerName,
this.customerEmail,
this.targetModelName, this.targetModelName,
this.sourceModelName, this.sourceModelName,
this.createdById, this.createdById,
@@ -192,6 +194,7 @@ class TicketModel extends Equatable {
TicketResult? ticketResult, TicketResult? ticketResult,
String? resolutionNotes, String? resolutionNotes,
String? customerName, String? customerName,
String? customerEmail,
String? targetModelName, String? targetModelName,
String? sourceModelName, String? sourceModelName,
String? createdById, String? createdById,
@@ -228,6 +231,7 @@ class TicketModel extends Equatable {
ticketResult: ticketResult ?? this.ticketResult, ticketResult: ticketResult ?? this.ticketResult,
resolutionNotes: resolutionNotes ?? this.resolutionNotes, resolutionNotes: resolutionNotes ?? this.resolutionNotes,
customerName: customerName ?? this.customerName, customerName: customerName ?? this.customerName,
customerEmail: customerEmail ?? this.customerEmail,
targetModelName: targetModelName ?? this.targetModelName, targetModelName: targetModelName ?? this.targetModelName,
sourceModelName: sourceModelName ?? this.sourceModelName, sourceModelName: sourceModelName ?? this.sourceModelName,
createdById: createdById ?? this.createdById, createdById: createdById ?? this.createdById,
@@ -276,6 +280,7 @@ class TicketModel extends Equatable {
ticketResult: TicketResult.fromString(map['ticket_result'] as String?), ticketResult: TicketResult.fromString(map['ticket_result'] as String?),
resolutionNotes: map['resolution_notes'] as String?, resolutionNotes: map['resolution_notes'] as String?,
customerName: (map['customer']?['name'] as String?).myFormat(), customerName: (map['customer']?['name'] as String?).myFormat(),
customerEmail: (map['customer']?['email'] as String?).myFormat(),
targetModelName: (map['target_model']?['name_with_brand'] as String?) targetModelName: (map['target_model']?['name_with_brand'] as String?)
?.myFormat(), ?.myFormat(),
sourceModelName: (map['source_model']?['name_with_brand'] as String?) sourceModelName: (map['source_model']?['name_with_brand'] as String?)

View File

@@ -1,14 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/widgets/shared_forms/customer_section.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/model_section.dart';
import 'package:flux/core/widgets/shared_forms/shared_files_section.dart'; import 'package:flux/core/widgets/shared_forms/shared_files_section.dart';
import 'package:flux/features/attachments/blocs/attachments_bloc.dart'; import 'package:flux/features/attachments/blocs/attachments_bloc.dart';
import 'package:flux/features/company/models/company_model.dart';
import 'package:flux/features/tickets/blocs/ticket_form_cubit.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_form_state.dart';
import 'package:flux/features/tickets/models/ticket_model.dart'; import 'package:flux/features/tickets/models/ticket_model.dart';
import 'package:flux/core/widgets/shared_forms/staff_section.dart'; import 'package:flux/core/widgets/shared_forms/staff_section.dart';
import 'package:flux/features/tickets/models/ticket_status_extension.dart'; import 'package:flux/features/tickets/models/ticket_status_extension.dart';
import 'package:flux/features/tickets/utils/ticket_pdf_service.dart';
import 'package:get_it/get_it.dart';
import 'package:printing/printing.dart';
class TicketFormScreen extends StatefulWidget { class TicketFormScreen extends StatefulWidget {
final TicketModel? existingTicket; final TicketModel? existingTicket;
@@ -121,6 +126,102 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
return newId; return newId;
} }
void _showSuccessActions(
BuildContext context,
TicketModel ticket,
CompanyModel company,
) {
showModalBottomSheet(
context: context,
isDismissible: false, // Costringiamo l'operatore a una scelta conscia
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.vertical(top: Radius.circular(24)),
),
builder: (context) => Container(
padding: const EdgeInsets.all(24),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 40,
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(10),
),
),
const SizedBox(height: 24),
const Icon(Icons.check_circle, color: Colors.green, size: 64),
const SizedBox(height: 16),
Text(
"Ticket Salvato!",
style: Theme.of(
context,
).textTheme.headlineSmall?.copyWith(fontWeight: FontWeight.bold),
),
Text(
"Rif: ${ticket.referenceId}",
style: TextStyle(color: Colors.grey[600], fontSize: 18),
),
const SizedBox(height: 32),
// Griglia delle Azioni
GridView.count(
crossAxisCount: 2,
shrinkWrap: true,
mainAxisSpacing: 16,
crossAxisSpacing: 16,
childAspectRatio: 1.5,
children: [
_ActionButton(
icon: Icons.print,
label: "Ricevuta A4",
onTap: () async {
final pdf = await TicketPdfService().generateTicketReceipt(
ticket,
company,
);
await Printing.layoutPdf(onLayout: (format) => pdf);
},
),
if (company.labelFormat != LabelFormat.none)
_ActionButton(
icon: Icons.label,
label: "Etichetta",
onTap: () async {
final pdf = await TicketPdfService().generateLabelPdf(
ticket,
company,
);
await Printing.layoutPdf(onLayout: (format) => pdf);
},
),
_ActionButton(
icon: Icons.email,
label: "Invia Email",
onTap: ticket.customerEmail != null
? () => _sendEmail(ticket)
: null,
),
_ActionButton(
icon: Icons.close,
label: "Chiudi",
color: Colors.blue[900],
textColor: Colors.white,
onTap: () => Navigator.of(context).pop(), // Torna alla lista
),
],
),
],
),
),
);
}
Future<void> _sendEmail(TicketModel ticket) async {
//TODO send ticket receipt via email
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
@@ -133,7 +234,11 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
} }
if (state.status == TicketFormStatus.success) { if (state.status == TicketFormStatus.success) {
Navigator.of(context).pop(); _showSuccessActions(
context,
state.ticket,
GetIt.I.get<SessionCubit>().state.company!,
);
} else if (state.status == TicketFormStatus.successAndAddAnother) { } else if (state.status == TicketFormStatus.successAndAddAnother) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
const SnackBar( const SnackBar(
@@ -597,3 +702,55 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
); );
} }
} }
// Widget helper per i bottoni dell'Action Hub
class _ActionButton extends StatelessWidget {
final IconData icon;
final String label;
final VoidCallback? onTap;
final Color? color;
final Color? textColor;
const _ActionButton({
required this.icon,
required this.label,
this.onTap,
this.color,
this.textColor,
});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: onTap,
borderRadius: BorderRadius.circular(16),
child: Container(
decoration: BoxDecoration(
color: onTap == null ? Colors.grey[100] : (color ?? Colors.grey[200]),
borderRadius: BorderRadius.circular(16),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
icon,
color: onTap == null
? Colors.grey
: (textColor ?? Colors.blue[900]),
),
const SizedBox(height: 8),
Text(
label,
style: TextStyle(
fontWeight: FontWeight.bold,
color: onTap == null
? Colors.grey
: (textColor ?? Colors.blue[900]),
),
),
],
),
),
);
}
}