feat-tickets #14
@@ -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/models/customer_model.dart';
|
||||
import 'package:flux/features/customers/ui/quick_customer_dialog.dart';
|
||||
import 'package:flux/features/operations/models/operation_model.dart';
|
||||
|
||||
class SharedCustomerSection extends StatelessWidget {
|
||||
final OperationModel? currentOp;
|
||||
final String? customerId;
|
||||
final String? customerName;
|
||||
final ValueChanged<CustomerModel> onCustomerSelected;
|
||||
|
||||
const SharedCustomerSection({
|
||||
super.key,
|
||||
required this.currentOp,
|
||||
this.customerId,
|
||||
this.customerName,
|
||||
required this.onCustomerSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final hasCustomer =
|
||||
currentOp?.customerId != null && currentOp!.customerId!.isNotEmpty;
|
||||
final hasCustomer = customerId != null && customerId!.isNotEmpty;
|
||||
final theme = Theme.of(context);
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -47,9 +47,7 @@ class SharedCustomerSection extends StatelessWidget {
|
||||
const SizedBox(width: 12),
|
||||
Expanded(
|
||||
child: Text(
|
||||
hasCustomer
|
||||
? currentOp!.customerDisplayName!
|
||||
: 'Seleziona Cliente *',
|
||||
hasCustomer ? customerName! : 'Seleziona Cliente *',
|
||||
style: TextStyle(
|
||||
fontWeight: hasCustomer
|
||||
? FontWeight.bold
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:flutter_bloc/flutter_bloc.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/models/staff_member_model.dart';
|
||||
import 'package:flux/features/operations/models/operation_model.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
class StaffSection extends StatelessWidget {
|
||||
@@ -24,8 +23,7 @@ class StaffSection extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final selectedStaffId =
|
||||
currentOp?.staffId ??
|
||||
GetIt.I.get<SessionCubit>().state.currentStaffMember?.id;
|
||||
staffId ?? GetIt.I.get<SessionCubit>().state.currentStaffMember?.id;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
||||
@@ -319,7 +319,8 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
StaffSection(
|
||||
currentOp: currentOp,
|
||||
staffId: currentOp?.staffId,
|
||||
staffName: currentOp?.staffDisplayName,
|
||||
onStaffSelected: (staff) => {
|
||||
context.read<OperationsCubit>().updateOperationFields(
|
||||
staffId: staff.id,
|
||||
@@ -330,7 +331,8 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
const Divider(height: 50),
|
||||
_buildSectionTitle('Cliente & Riferimento'),
|
||||
SharedCustomerSection(
|
||||
currentOp: currentOp,
|
||||
customerId: currentOp?.customerId,
|
||||
customerName: currentOp?.customerDisplayName,
|
||||
onCustomerSelected: (customer) {
|
||||
context.read<OperationsCubit>().updateOperationFields(
|
||||
customerId: customer.id,
|
||||
|
||||
@@ -34,7 +34,7 @@ class TicketFormCubit extends Cubit<TicketFormState> {
|
||||
createdById: currentUser?.id,
|
||||
createdByName: currentUser?.name,
|
||||
// Impostiamo lo stato iniziale
|
||||
status: TicketStatus.open,
|
||||
ticketStatus: TicketStatus.open,
|
||||
ticketType: TicketType.repair, // Default
|
||||
);
|
||||
|
||||
@@ -98,7 +98,7 @@ class TicketFormCubit extends Cubit<TicketFormState> {
|
||||
state.copyWith(
|
||||
ticket: state.ticket.copyWith(
|
||||
ticketType: ticketType ?? state.ticket.ticketType,
|
||||
status: status ?? state.ticket.status,
|
||||
ticketStatus: status ?? state.ticket.ticketStatus,
|
||||
request: request ?? state.ticket.request,
|
||||
targetSn: targetSn ?? state.ticket.targetSn,
|
||||
alternativePhoneNumber:
|
||||
@@ -143,7 +143,7 @@ class TicketFormCubit extends Cubit<TicketFormState> {
|
||||
createdById: ticketToSave
|
||||
.createdById, // Manteniamo quello selezionato nella tendina!
|
||||
createdByName: ticketToSave.createdByName,
|
||||
status: TicketStatus.open,
|
||||
ticketStatus: TicketStatus.open,
|
||||
ticketType: TicketType.repair,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -27,7 +27,8 @@ class TicketRepository {
|
||||
.select('''
|
||||
*,
|
||||
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 (*),
|
||||
source_model:model!ticket_model_id_2_fkey (*)
|
||||
''')
|
||||
@@ -83,7 +84,8 @@ class TicketRepository {
|
||||
.select('''
|
||||
*,
|
||||
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 (*),
|
||||
source_model:model!ticket_model_id_2_fkey (*)
|
||||
''')
|
||||
@@ -144,8 +146,8 @@ class TicketRepository {
|
||||
// 2. Filtriamo in memoria!
|
||||
final urgentTickets = allStoreTickets.where((ticket) {
|
||||
// Escludiamo quelli già chiusi o consegnati
|
||||
if (ticket.status == TicketStatus.closed ||
|
||||
ticket.status == TicketStatus.ready) {
|
||||
if (ticket.ticketStatus == TicketStatus.closed ||
|
||||
ticket.ticketStatus == TicketStatus.ready) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -178,7 +180,8 @@ class TicketRepository {
|
||||
customer (*),
|
||||
target_model:model!ticket_model_id_1_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)
|
||||
.single();
|
||||
|
||||
@@ -35,8 +35,7 @@ enum TicketStatus {
|
||||
final String displayValue;
|
||||
const TicketStatus(this.value, this.displayValue);
|
||||
|
||||
static TicketStatus? fromString(String? val) {
|
||||
if (val == null) return null;
|
||||
static TicketStatus fromString(String? val) {
|
||||
return TicketStatus.values.firstWhere(
|
||||
(e) => e.value == val,
|
||||
orElse: () => TicketStatus.open,
|
||||
@@ -103,9 +102,9 @@ class TicketModel extends Equatable {
|
||||
final String? alternativePhoneNumber;
|
||||
final bool hasCourtesyDevice;
|
||||
final TicketType ticketType;
|
||||
final TicketStatus? status;
|
||||
final TicketStatus ticketStatus;
|
||||
final DateTime? estimatedDeliveryAt;
|
||||
final TicketResult? result;
|
||||
final TicketResult? ticketResult;
|
||||
final String? resolutionNotes;
|
||||
final String? legacyId;
|
||||
final String? customerName;
|
||||
@@ -139,9 +138,9 @@ class TicketModel extends Equatable {
|
||||
this.alternativePhoneNumber,
|
||||
this.hasCourtesyDevice = false,
|
||||
required this.ticketType,
|
||||
this.status,
|
||||
this.ticketStatus = TicketStatus.closed,
|
||||
this.estimatedDeliveryAt,
|
||||
this.result,
|
||||
this.ticketResult,
|
||||
this.resolutionNotes,
|
||||
this.legacyId,
|
||||
this.customerName,
|
||||
@@ -160,7 +159,7 @@ class TicketModel extends Equatable {
|
||||
companyId: companyId ?? '',
|
||||
storeId: storeId,
|
||||
ticketType: TicketType.repair, // Valore di default
|
||||
status: TicketStatus.open,
|
||||
ticketStatus: TicketStatus.open,
|
||||
customerPrice: 0.0,
|
||||
internalCost: 0.0,
|
||||
hasCourtesyDevice: false,
|
||||
@@ -190,9 +189,9 @@ class TicketModel extends Equatable {
|
||||
String? alternativePhoneNumber,
|
||||
bool? hasCourtesyDevice,
|
||||
TicketType? ticketType,
|
||||
TicketStatus? status,
|
||||
TicketStatus? ticketStatus,
|
||||
DateTime? estimatedDeliveryAt,
|
||||
TicketResult? result,
|
||||
TicketResult? ticketResult,
|
||||
String? resolutionNotes,
|
||||
String? legacyId,
|
||||
String? customerName,
|
||||
@@ -227,9 +226,9 @@ class TicketModel extends Equatable {
|
||||
alternativePhoneNumber ?? this.alternativePhoneNumber,
|
||||
hasCourtesyDevice: hasCourtesyDevice ?? this.hasCourtesyDevice,
|
||||
ticketType: ticketType ?? this.ticketType,
|
||||
status: status ?? this.status,
|
||||
ticketStatus: ticketStatus ?? this.ticketStatus,
|
||||
estimatedDeliveryAt: estimatedDeliveryAt ?? this.estimatedDeliveryAt,
|
||||
result: result ?? this.result,
|
||||
ticketResult: ticketResult ?? this.ticketResult,
|
||||
resolutionNotes: resolutionNotes ?? this.resolutionNotes,
|
||||
legacyId: legacyId ?? this.legacyId,
|
||||
customerName: customerName ?? this.customerName,
|
||||
@@ -274,11 +273,11 @@ class TicketModel extends Equatable {
|
||||
alternativePhoneNumber: map['alternative_phone_number'] as String?,
|
||||
hasCourtesyDevice: map['has_courtesy_device'] as bool? ?? false,
|
||||
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
|
||||
? DateTime.parse(map['estimated_delivery_at']).toLocal()
|
||||
: null,
|
||||
result: TicketResult.fromString(map['result'] as String?),
|
||||
ticketResult: TicketResult.fromString(map['ticket_result'] as String?),
|
||||
resolutionNotes: map['resolution_notes'] as String?,
|
||||
legacyId: map['legacy_id'] as String?,
|
||||
customerName: (map['customer']?['name'] as String?).myFormat(),
|
||||
@@ -318,10 +317,10 @@ class TicketModel extends Equatable {
|
||||
'alternative_phone_number': alternativePhoneNumber,
|
||||
'has_courtesy_device': hasCourtesyDevice,
|
||||
'ticket_type': ticketType.value,
|
||||
if (status != null) 'status': status!.value,
|
||||
'ticket_status': ticketStatus.value,
|
||||
if (estimatedDeliveryAt != null)
|
||||
'estimated_delivery_at': estimatedDeliveryAt!.toUtc().toIso8601String(),
|
||||
if (result != null) 'result': result!.value,
|
||||
if (ticketResult != null) 'ticket_result': ticketResult!.value,
|
||||
'resolution_notes': resolutionNotes,
|
||||
'legacy_id': legacyId,
|
||||
'included_accessories': includedAccessories,
|
||||
@@ -351,9 +350,9 @@ class TicketModel extends Equatable {
|
||||
alternativePhoneNumber,
|
||||
hasCourtesyDevice,
|
||||
ticketType,
|
||||
status,
|
||||
ticketStatus,
|
||||
estimatedDeliveryAt,
|
||||
result,
|
||||
ticketResult,
|
||||
resolutionNotes,
|
||||
legacyId,
|
||||
includedAccessories,
|
||||
|
||||
@@ -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/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_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 {
|
||||
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
|
||||
void _syncTextControllers(TicketModel model) {
|
||||
if (_altPhoneCtrl.text.isEmpty)
|
||||
if (_altPhoneCtrl.text.isEmpty) {
|
||||
_altPhoneCtrl.text = model.alternativePhoneNumber ?? '';
|
||||
}
|
||||
if (_serialCtrl.text.isEmpty) _serialCtrl.text = model.targetSn ?? '';
|
||||
if (_requestCtrl.text.isEmpty) _requestCtrl.text = model.request ?? '';
|
||||
if (_accessoriesCtrl.text.isEmpty)
|
||||
if (_requestCtrl.text.isEmpty) _requestCtrl.text = model.request;
|
||||
if (_accessoriesCtrl.text.isEmpty) {
|
||||
_accessoriesCtrl.text = model.includedAccessories ?? '';
|
||||
if (_publicNotesCtrl.text.isEmpty)
|
||||
}
|
||||
if (_publicNotesCtrl.text.isEmpty) {
|
||||
_publicNotesCtrl.text = model.publicNotes ?? '';
|
||||
if (_internalNotesCtrl.text.isEmpty)
|
||||
}
|
||||
if (_internalNotesCtrl.text.isEmpty) {
|
||||
_internalNotesCtrl.text = model.internalNotes ?? '';
|
||||
if (_priceCtrl.text.isEmpty && model.customerPrice > 0)
|
||||
}
|
||||
if (_priceCtrl.text.isEmpty && model.customerPrice > 0) {
|
||||
_priceCtrl.text = model.customerPrice.toString();
|
||||
if (_costCtrl.text.isEmpty && model.internalCost > 0)
|
||||
}
|
||||
if (_costCtrl.text.isEmpty && model.internalCost > 0) {
|
||||
_costCtrl.text = model.internalCost.toString();
|
||||
}
|
||||
_isInitialized = true;
|
||||
}
|
||||
|
||||
@@ -143,10 +150,10 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: Chip(
|
||||
label: Text(
|
||||
ticket.status.name.toUpperCase(),
|
||||
ticket.ticketStatus!.name.toUpperCase(),
|
||||
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,
|
||||
onStaffSelected: (staff) =>
|
||||
context.read<TicketFormCubit>().updateCreator(
|
||||
staffId: staff.id,
|
||||
staffId: staff.id!,
|
||||
staffName: staff.name,
|
||||
),
|
||||
),
|
||||
@@ -254,7 +261,7 @@ class _TicketFormScreenState extends State<TicketFormScreen> {
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: DropdownButtonFormField<TicketStatus>(
|
||||
value: ticket.status,
|
||||
value: ticket.ticketStatus,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Stato Attuale',
|
||||
),
|
||||
|
||||
@@ -192,8 +192,8 @@ class _TicketCard extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final statusColor = ticket.status?.color ?? Colors.grey;
|
||||
final statusIcon = ticket.status?.icon ?? Icons.help_outline;
|
||||
final statusColor = ticket.ticketStatus.color;
|
||||
final statusIcon = ticket.ticketStatus.icon;
|
||||
|
||||
return Card(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 4.0),
|
||||
@@ -244,7 +244,7 @@ class _TicketCard extends StatelessWidget {
|
||||
Icon(statusIcon, size: 14, color: statusColor),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
ticket.status?.displayValue ?? 'N/D',
|
||||
ticket.ticketStatus.displayValue,
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: statusColor,
|
||||
|
||||
Reference in New Issue
Block a user