This commit is contained in:
2026-05-06 19:25:17 +02:00
parent d15d7e458b
commit 040db4ad79
8 changed files with 60 additions and 53 deletions

View File

@@ -3,22 +3,22 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/customers/blocs/customers_cubit.dart'; import 'package:flux/features/customers/blocs/customers_cubit.dart';
import 'package:flux/features/customers/models/customer_model.dart'; import 'package:flux/features/customers/models/customer_model.dart';
import 'package:flux/features/customers/ui/quick_customer_dialog.dart'; import 'package:flux/features/customers/ui/quick_customer_dialog.dart';
import 'package:flux/features/operations/models/operation_model.dart';
class SharedCustomerSection extends StatelessWidget { class SharedCustomerSection extends StatelessWidget {
final OperationModel? currentOp; final String? customerId;
final String? customerName;
final ValueChanged<CustomerModel> onCustomerSelected; final ValueChanged<CustomerModel> onCustomerSelected;
const SharedCustomerSection({ const SharedCustomerSection({
super.key, super.key,
required this.currentOp, this.customerId,
this.customerName,
required this.onCustomerSelected, required this.onCustomerSelected,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final hasCustomer = final hasCustomer = customerId != null && customerId!.isNotEmpty;
currentOp?.customerId != null && currentOp!.customerId!.isNotEmpty;
final theme = Theme.of(context); final theme = Theme.of(context);
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -47,9 +47,7 @@ class SharedCustomerSection extends StatelessWidget {
const SizedBox(width: 12), const SizedBox(width: 12),
Expanded( Expanded(
child: Text( child: Text(
hasCustomer hasCustomer ? customerName! : 'Seleziona Cliente *',
? currentOp!.customerDisplayName!
: 'Seleziona Cliente *',
style: TextStyle( style: TextStyle(
fontWeight: hasCustomer fontWeight: hasCustomer
? FontWeight.bold ? FontWeight.bold

View File

@@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart'; import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart'; import 'package:flux/features/master_data/staff/blocs/staff_cubit.dart';
import 'package:flux/features/master_data/staff/models/staff_member_model.dart'; import 'package:flux/features/master_data/staff/models/staff_member_model.dart';
import 'package:flux/features/operations/models/operation_model.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
class StaffSection extends StatelessWidget { class StaffSection extends StatelessWidget {
@@ -24,8 +23,7 @@ class StaffSection extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final theme = Theme.of(context); final theme = Theme.of(context);
final selectedStaffId = final selectedStaffId =
currentOp?.staffId ?? staffId ?? GetIt.I.get<SessionCubit>().state.currentStaffMember?.id;
GetIt.I.get<SessionCubit>().state.currentStaffMember?.id;
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,

View File

@@ -319,7 +319,8 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
StaffSection( StaffSection(
currentOp: currentOp, staffId: currentOp?.staffId,
staffName: currentOp?.staffDisplayName,
onStaffSelected: (staff) => { onStaffSelected: (staff) => {
context.read<OperationsCubit>().updateOperationFields( context.read<OperationsCubit>().updateOperationFields(
staffId: staff.id, staffId: staff.id,
@@ -330,7 +331,8 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
const Divider(height: 50), const Divider(height: 50),
_buildSectionTitle('Cliente & Riferimento'), _buildSectionTitle('Cliente & Riferimento'),
SharedCustomerSection( SharedCustomerSection(
currentOp: currentOp, customerId: currentOp?.customerId,
customerName: currentOp?.customerDisplayName,
onCustomerSelected: (customer) { onCustomerSelected: (customer) {
context.read<OperationsCubit>().updateOperationFields( context.read<OperationsCubit>().updateOperationFields(
customerId: customer.id, customerId: customer.id,

View File

@@ -34,7 +34,7 @@ class TicketFormCubit extends Cubit<TicketFormState> {
createdById: currentUser?.id, createdById: currentUser?.id,
createdByName: currentUser?.name, createdByName: currentUser?.name,
// Impostiamo lo stato iniziale // Impostiamo lo stato iniziale
status: TicketStatus.open, ticketStatus: TicketStatus.open,
ticketType: TicketType.repair, // Default ticketType: TicketType.repair, // Default
); );
@@ -98,7 +98,7 @@ class TicketFormCubit extends Cubit<TicketFormState> {
state.copyWith( state.copyWith(
ticket: state.ticket.copyWith( ticket: state.ticket.copyWith(
ticketType: ticketType ?? state.ticket.ticketType, ticketType: ticketType ?? state.ticket.ticketType,
status: status ?? state.ticket.status, ticketStatus: status ?? state.ticket.ticketStatus,
request: request ?? state.ticket.request, request: request ?? state.ticket.request,
targetSn: targetSn ?? state.ticket.targetSn, targetSn: targetSn ?? state.ticket.targetSn,
alternativePhoneNumber: alternativePhoneNumber:
@@ -143,7 +143,7 @@ class TicketFormCubit extends Cubit<TicketFormState> {
createdById: ticketToSave createdById: ticketToSave
.createdById, // Manteniamo quello selezionato nella tendina! .createdById, // Manteniamo quello selezionato nella tendina!
createdByName: ticketToSave.createdByName, createdByName: ticketToSave.createdByName,
status: TicketStatus.open, ticketStatus: TicketStatus.open,
ticketType: TicketType.repair, ticketType: TicketType.repair,
), ),
), ),

View File

@@ -27,7 +27,8 @@ class TicketRepository {
.select(''' .select('''
*, *,
customer (*), customer (*),
staff_member (*), created_by:staff_member!ticket_staff_id_fkey (*),
assigned_to:staff_member!ticket_assigned_to_id_fkey (*),
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 +84,8 @@ class TicketRepository {
.select(''' .select('''
*, *,
customer (*), customer (*),
staff_member (*), created_by:staff_member!ticket_staff_id_fkey (*),
assigned_to:staff_member!ticket_assigned_to_id_fkey (*),
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 (*)
''') ''')
@@ -144,8 +146,8 @@ class TicketRepository {
// 2. Filtriamo in memoria! // 2. Filtriamo in memoria!
final urgentTickets = allStoreTickets.where((ticket) { final urgentTickets = allStoreTickets.where((ticket) {
// Escludiamo quelli già chiusi o consegnati // Escludiamo quelli già chiusi o consegnati
if (ticket.status == TicketStatus.closed || if (ticket.ticketStatus == TicketStatus.closed ||
ticket.status == TicketStatus.ready) { ticket.ticketStatus == TicketStatus.ready) {
return false; return false;
} }
@@ -178,7 +180,8 @@ class TicketRepository {
customer (*), customer (*),
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 (*),
staff:staff_member!ticket_staff_id_fkey (*) created_by:staff_member!ticket_staff_id_fkey (*),
assigned_to:staff_member!ticket_assigned_to_id_fkey (*),
''') ''')
.eq('id', ticketId) .eq('id', ticketId)
.single(); .single();

View File

@@ -35,8 +35,7 @@ enum TicketStatus {
final String displayValue; final String displayValue;
const TicketStatus(this.value, this.displayValue); const TicketStatus(this.value, this.displayValue);
static TicketStatus? fromString(String? val) { static TicketStatus fromString(String? val) {
if (val == null) return null;
return TicketStatus.values.firstWhere( return TicketStatus.values.firstWhere(
(e) => e.value == val, (e) => e.value == val,
orElse: () => TicketStatus.open, orElse: () => TicketStatus.open,
@@ -103,9 +102,9 @@ class TicketModel extends Equatable {
final String? alternativePhoneNumber; final String? alternativePhoneNumber;
final bool hasCourtesyDevice; final bool hasCourtesyDevice;
final TicketType ticketType; final TicketType ticketType;
final TicketStatus? status; final TicketStatus ticketStatus;
final DateTime? estimatedDeliveryAt; final DateTime? estimatedDeliveryAt;
final TicketResult? result; final TicketResult? ticketResult;
final String? resolutionNotes; final String? resolutionNotes;
final String? legacyId; final String? legacyId;
final String? customerName; final String? customerName;
@@ -139,9 +138,9 @@ class TicketModel extends Equatable {
this.alternativePhoneNumber, this.alternativePhoneNumber,
this.hasCourtesyDevice = false, this.hasCourtesyDevice = false,
required this.ticketType, required this.ticketType,
this.status, this.ticketStatus = TicketStatus.closed,
this.estimatedDeliveryAt, this.estimatedDeliveryAt,
this.result, this.ticketResult,
this.resolutionNotes, this.resolutionNotes,
this.legacyId, this.legacyId,
this.customerName, this.customerName,
@@ -160,7 +159,7 @@ class TicketModel extends Equatable {
companyId: companyId ?? '', companyId: companyId ?? '',
storeId: storeId, storeId: storeId,
ticketType: TicketType.repair, // Valore di default ticketType: TicketType.repair, // Valore di default
status: TicketStatus.open, ticketStatus: TicketStatus.open,
customerPrice: 0.0, customerPrice: 0.0,
internalCost: 0.0, internalCost: 0.0,
hasCourtesyDevice: false, hasCourtesyDevice: false,
@@ -190,9 +189,9 @@ class TicketModel extends Equatable {
String? alternativePhoneNumber, String? alternativePhoneNumber,
bool? hasCourtesyDevice, bool? hasCourtesyDevice,
TicketType? ticketType, TicketType? ticketType,
TicketStatus? status, TicketStatus? ticketStatus,
DateTime? estimatedDeliveryAt, DateTime? estimatedDeliveryAt,
TicketResult? result, TicketResult? ticketResult,
String? resolutionNotes, String? resolutionNotes,
String? legacyId, String? legacyId,
String? customerName, String? customerName,
@@ -227,9 +226,9 @@ class TicketModel extends Equatable {
alternativePhoneNumber ?? this.alternativePhoneNumber, alternativePhoneNumber ?? this.alternativePhoneNumber,
hasCourtesyDevice: hasCourtesyDevice ?? this.hasCourtesyDevice, hasCourtesyDevice: hasCourtesyDevice ?? this.hasCourtesyDevice,
ticketType: ticketType ?? this.ticketType, ticketType: ticketType ?? this.ticketType,
status: status ?? this.status, ticketStatus: ticketStatus ?? this.ticketStatus,
estimatedDeliveryAt: estimatedDeliveryAt ?? this.estimatedDeliveryAt, estimatedDeliveryAt: estimatedDeliveryAt ?? this.estimatedDeliveryAt,
result: result ?? this.result, ticketResult: ticketResult ?? this.ticketResult,
resolutionNotes: resolutionNotes ?? this.resolutionNotes, resolutionNotes: resolutionNotes ?? this.resolutionNotes,
legacyId: legacyId ?? this.legacyId, legacyId: legacyId ?? this.legacyId,
customerName: customerName ?? this.customerName, customerName: customerName ?? this.customerName,
@@ -274,11 +273,11 @@ class TicketModel extends Equatable {
alternativePhoneNumber: map['alternative_phone_number'] as String?, alternativePhoneNumber: map['alternative_phone_number'] as String?,
hasCourtesyDevice: map['has_courtesy_device'] as bool? ?? false, hasCourtesyDevice: map['has_courtesy_device'] as bool? ?? false,
ticketType: TicketType.fromString(map['ticket_type'] as String), ticketType: TicketType.fromString(map['ticket_type'] as String),
status: TicketStatus.fromString(map['status'] as String?), ticketStatus: TicketStatus.fromString(map['ticket_status'] as String),
estimatedDeliveryAt: map['estimated_delivery_at'] != null estimatedDeliveryAt: map['estimated_delivery_at'] != null
? DateTime.parse(map['estimated_delivery_at']).toLocal() ? DateTime.parse(map['estimated_delivery_at']).toLocal()
: null, : null,
result: TicketResult.fromString(map['result'] as String?), ticketResult: TicketResult.fromString(map['ticket_result'] as String?),
resolutionNotes: map['resolution_notes'] as String?, resolutionNotes: map['resolution_notes'] as String?,
legacyId: map['legacy_id'] as String?, legacyId: map['legacy_id'] as String?,
customerName: (map['customer']?['name'] as String?).myFormat(), customerName: (map['customer']?['name'] as String?).myFormat(),
@@ -318,10 +317,10 @@ class TicketModel extends Equatable {
'alternative_phone_number': alternativePhoneNumber, 'alternative_phone_number': alternativePhoneNumber,
'has_courtesy_device': hasCourtesyDevice, 'has_courtesy_device': hasCourtesyDevice,
'ticket_type': ticketType.value, 'ticket_type': ticketType.value,
if (status != null) 'status': status!.value, 'ticket_status': ticketStatus.value,
if (estimatedDeliveryAt != null) if (estimatedDeliveryAt != null)
'estimated_delivery_at': estimatedDeliveryAt!.toUtc().toIso8601String(), 'estimated_delivery_at': estimatedDeliveryAt!.toUtc().toIso8601String(),
if (result != null) 'result': result!.value, if (ticketResult != null) 'ticket_result': ticketResult!.value,
'resolution_notes': resolutionNotes, 'resolution_notes': resolutionNotes,
'legacy_id': legacyId, 'legacy_id': legacyId,
'included_accessories': includedAccessories, 'included_accessories': includedAccessories,
@@ -351,9 +350,9 @@ class TicketModel extends Equatable {
alternativePhoneNumber, alternativePhoneNumber,
hasCourtesyDevice, hasCourtesyDevice,
ticketType, ticketType,
status, ticketStatus,
estimatedDeliveryAt, estimatedDeliveryAt,
result, ticketResult,
resolutionNotes, resolutionNotes,
legacyId, legacyId,
includedAccessories, includedAccessories,

View File

@@ -5,7 +5,8 @@ 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/shared_customer_section.dart'; import 'package:flux/core/widgets/shared_forms/shared_customer_section.dart';
import 'package:flux/core/widgets/shared_forms/shared_model_section.dart'; import 'package:flux/core/widgets/shared_forms/shared_model_section.dart';
import 'package:flux/core/widgets/shared_forms/shared_staff_section.dart'; // Il tuo widget agnostico dello staff import 'package:flux/core/widgets/shared_forms/shared_staff_section.dart';
import 'package:flux/features/tickets/models/ticket_status_extension.dart'; // Il tuo widget agnostico dello staff
class TicketFormScreen extends StatefulWidget { class TicketFormScreen extends StatefulWidget {
final TicketModel? existingTicket; final TicketModel? existingTicket;
@@ -53,20 +54,26 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
// Sincronizza i controller con lo stato iniziale senza sovrascrivere se l'utente sta digitando // Sincronizza i controller con lo stato iniziale senza sovrascrivere se l'utente sta digitando
void _syncTextControllers(TicketModel model) { void _syncTextControllers(TicketModel model) {
if (_altPhoneCtrl.text.isEmpty) if (_altPhoneCtrl.text.isEmpty) {
_altPhoneCtrl.text = model.alternativePhoneNumber ?? ''; _altPhoneCtrl.text = model.alternativePhoneNumber ?? '';
}
if (_serialCtrl.text.isEmpty) _serialCtrl.text = model.targetSn ?? ''; if (_serialCtrl.text.isEmpty) _serialCtrl.text = model.targetSn ?? '';
if (_requestCtrl.text.isEmpty) _requestCtrl.text = model.request ?? ''; if (_requestCtrl.text.isEmpty) _requestCtrl.text = model.request;
if (_accessoriesCtrl.text.isEmpty) if (_accessoriesCtrl.text.isEmpty) {
_accessoriesCtrl.text = model.includedAccessories ?? ''; _accessoriesCtrl.text = model.includedAccessories ?? '';
if (_publicNotesCtrl.text.isEmpty) }
if (_publicNotesCtrl.text.isEmpty) {
_publicNotesCtrl.text = model.publicNotes ?? ''; _publicNotesCtrl.text = model.publicNotes ?? '';
if (_internalNotesCtrl.text.isEmpty) }
if (_internalNotesCtrl.text.isEmpty) {
_internalNotesCtrl.text = model.internalNotes ?? ''; _internalNotesCtrl.text = model.internalNotes ?? '';
if (_priceCtrl.text.isEmpty && model.customerPrice > 0) }
if (_priceCtrl.text.isEmpty && model.customerPrice > 0) {
_priceCtrl.text = model.customerPrice.toString(); _priceCtrl.text = model.customerPrice.toString();
if (_costCtrl.text.isEmpty && model.internalCost > 0) }
if (_costCtrl.text.isEmpty && model.internalCost > 0) {
_costCtrl.text = model.internalCost.toString(); _costCtrl.text = model.internalCost.toString();
}
_isInitialized = true; _isInitialized = true;
} }
@@ -143,10 +150,10 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
padding: const EdgeInsets.only(right: 16.0), padding: const EdgeInsets.only(right: 16.0),
child: Chip( child: Chip(
label: Text( label: Text(
ticket.status.name.toUpperCase(), ticket.ticketStatus!.name.toUpperCase(),
style: const TextStyle(color: Colors.white, fontSize: 10), style: const TextStyle(color: Colors.white, fontSize: 10),
), ),
backgroundColor: ticket.status.color, backgroundColor: ticket.ticketStatus.color,
), ),
), ),
], ],
@@ -174,7 +181,7 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
staffName: ticket.createdByName, staffName: ticket.createdByName,
onStaffSelected: (staff) => onStaffSelected: (staff) =>
context.read<TicketFormCubit>().updateCreator( context.read<TicketFormCubit>().updateCreator(
staffId: staff.id, staffId: staff.id!,
staffName: staff.name, staffName: staff.name,
), ),
), ),
@@ -254,7 +261,7 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
const SizedBox(width: 16), const SizedBox(width: 16),
Expanded( Expanded(
child: DropdownButtonFormField<TicketStatus>( child: DropdownButtonFormField<TicketStatus>(
value: ticket.status, value: ticket.ticketStatus,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Stato Attuale', labelText: 'Stato Attuale',
), ),

View File

@@ -192,8 +192,8 @@ class _TicketCard extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final statusColor = ticket.status?.color ?? Colors.grey; final statusColor = ticket.ticketStatus.color;
final statusIcon = ticket.status?.icon ?? Icons.help_outline; final statusIcon = ticket.ticketStatus.icon;
return Card( return Card(
margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0), margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
@@ -244,7 +244,7 @@ class _TicketCard extends StatelessWidget {
Icon(statusIcon, size: 14, color: statusColor), Icon(statusIcon, size: 14, color: statusColor),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Text(
ticket.status?.displayValue ?? 'N/D', ticket.ticketStatus.displayValue,
style: TextStyle( style: TextStyle(
fontSize: 12, fontSize: 12,
color: statusColor, color: statusColor,