maremma maiala impestata, buonissima base dopo ultra refactor

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
2026-05-02 12:19:04 +02:00
parent 1721b2ff89
commit 67e8b8b654
23 changed files with 706 additions and 2730 deletions

View File

@@ -6,31 +6,31 @@ import 'package:flux/features/customers/data/customer_repository.dart';
import 'package:flux/features/customers/models/customer_model.dart';
import 'package:get_it/get_it.dart';
part 'customer_state.dart';
part 'customers_state.dart';
class CustomerCubit extends Cubit<CustomerState> {
class CustomersCubit extends Cubit<CustomersState> {
final CustomerRepository _repository = GetIt.I<CustomerRepository>();
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
// Variabile per gestire il debounce della ricerca
Timer? _searchDebounce;
CustomerCubit() : super(const CustomerState());
CustomersCubit() : super(const CustomersState());
// --- LETTURA ---
Future<void> loadCustomers() async {
emit(state.copyWith(status: CustomerStatus.loading));
emit(state.copyWith(status: CustomersStatus.loading));
try {
final customers = await _repository.getCustomers(
_sessionCubit.state.company!.id!,
);
emit(
state.copyWith(status: CustomerStatus.success, customers: customers),
state.copyWith(status: CustomersStatus.success, customers: customers),
);
} catch (e) {
emit(
state.copyWith(
status: CustomerStatus.failure,
status: CustomersStatus.failure,
errorMessage: e.toString(),
),
);
@@ -39,7 +39,7 @@ class CustomerCubit extends Cubit<CustomerState> {
// --- CREAZIONE ---
Future<void> createCustomer(CustomerModel customer) async {
emit(state.copyWith(status: CustomerStatus.loading));
emit(state.copyWith(status: CustomersStatus.loading));
try {
final newCustomer = await _repository.saveCustomer(customer);
@@ -49,7 +49,7 @@ class CustomerCubit extends Cubit<CustomerState> {
emit(
state.copyWith(
status: CustomerStatus.success,
status: CustomersStatus.success,
customers: updatedList,
lastCreatedCustomer: newCustomer,
),
@@ -57,7 +57,7 @@ class CustomerCubit extends Cubit<CustomerState> {
} catch (e) {
emit(
state.copyWith(
status: CustomerStatus.failure,
status: CustomersStatus.failure,
errorMessage: e.toString(),
),
);
@@ -66,7 +66,7 @@ class CustomerCubit extends Cubit<CustomerState> {
// --- AGGIORNAMENTO ---
Future<void> updateCustomer(CustomerModel customer) async {
emit(state.copyWith(status: CustomerStatus.loading));
emit(state.copyWith(status: CustomersStatus.loading));
try {
final updatedCustomer = await _repository.updateCustomer(customer);
@@ -79,7 +79,7 @@ class CustomerCubit extends Cubit<CustomerState> {
emit(
state.copyWith(
status: CustomerStatus.success,
status: CustomersStatus.success,
customers: updatedList,
lastCreatedCustomer:
updatedCustomer, // Utile se modifichi un cliente appena creato
@@ -88,7 +88,7 @@ class CustomerCubit extends Cubit<CustomerState> {
} catch (e) {
emit(
state.copyWith(
status: CustomerStatus.failure,
status: CustomersStatus.failure,
errorMessage: e.toString(),
),
);
@@ -115,12 +115,12 @@ class CustomerCubit extends Cubit<CustomerState> {
query,
);
emit(
state.copyWith(status: CustomerStatus.success, customers: results),
state.copyWith(status: CustomersStatus.success, customers: results),
);
} catch (e) {
emit(
state.copyWith(
status: CustomerStatus.failure,
status: CustomersStatus.failure,
errorMessage: e.toString(),
),
);

View File

@@ -1,6 +1,6 @@
part of 'customer_cubit.dart';
part of 'customers_cubit.dart';
enum CustomerStatus {
enum CustomersStatus {
initial,
loading,
filesLoading,
@@ -9,26 +9,26 @@ enum CustomerStatus {
failure,
}
class CustomerState extends Equatable {
final CustomerStatus status;
class CustomersState extends Equatable {
final CustomersStatus status;
final List<CustomerModel> customers;
final CustomerModel? lastCreatedCustomer;
final String? errorMessage;
const CustomerState({
this.status = CustomerStatus.initial,
const CustomersState({
this.status = CustomersStatus.initial,
this.customers = const [],
this.lastCreatedCustomer,
this.errorMessage,
});
CustomerState copyWith({
CustomerStatus? status,
CustomersState copyWith({
CustomersStatus? status,
List<CustomerModel>? customers,
CustomerModel? lastCreatedCustomer,
String? errorMessage,
}) {
return CustomerState(
return CustomersState(
status: status ?? this.status,
customers: customers ?? this.customers,
lastCreatedCustomer: lastCreatedCustomer ?? this.lastCreatedCustomer,

View File

@@ -1,202 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/customers/blocs/customer_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/blocs/operations_cubit.dart';
class CustomerSearchSheet extends StatefulWidget {
const CustomerSearchSheet({super.key});
@override
State<CustomerSearchSheet> createState() => _CustomerSearchSheetState();
}
class _CustomerSearchSheetState extends State<CustomerSearchSheet> {
final TextEditingController _searchController = TextEditingController();
@override
void initState() {
super.initState();
context.read<CustomerCubit>().loadCustomers();
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
void _onSearchChanged(String query) {
context.read<CustomerCubit>().searchCustomers(query);
}
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height * 0.85,
),
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// --- HEADER ---
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"Trova Cliente",
style: TextStyle(fontSize: 22, fontWeight: FontWeight.bold),
),
IconButton(
icon: const Icon(Icons.close),
onPressed: () => Navigator.pop(context),
tooltip: "Chiudi",
),
],
),
const SizedBox(height: 16),
// --- BARRA DI RICERCA ---
TextField(
controller: _searchController,
decoration: InputDecoration(
hintText: "Cerca per nome, cognome o CF...",
prefixIcon: const Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
filled: true,
fillColor: Theme.of(
context,
).colorScheme.surfaceContainerHighest.withValues(alpha: 0.3),
suffixIcon: IconButton(
icon: const Icon(Icons.clear),
onPressed: () {
_searchController.clear();
_onSearchChanged("");
},
),
),
onChanged: _onSearchChanged,
),
const SizedBox(height: 16),
// --- TASTO NUOVO CLIENTE ---
SizedBox(
width: double.infinity,
child: IconButton(
icon: const Icon(Icons.person_add),
onPressed: () async {
final operationsCubit = context.read<OperationsCubit>();
// Apriamo la dialog passando la query attuale
final CustomerModel? nuovoCliente = await showDialog(
context: context,
builder: (context) => QuickCustomerDialog(
initialQuery: _searchController.text,
),
);
if (nuovoCliente != null) {
operationsCubit.updateField(
customerId: nuovoCliente.id,
customerDisplayName: nuovoCliente.name,
);
setState(() {
_searchController.clear();
});
}
},
),
),
const SizedBox(height: 24),
// --- LISTA RISULTATI CON BLOC BUILDER ---
const Text(
"Risultati",
style: TextStyle(fontWeight: FontWeight.bold, color: Colors.grey),
),
const SizedBox(height: 8),
Expanded(
// AGGANCIO AL CUBIT REALE
child: BlocBuilder<CustomerCubit, CustomerState>(
builder: (context, state) {
// 1. Stato di caricamento
if (state.status == CustomerStatus.loading) {
return const Center(child: CircularProgressIndicator());
}
// 2. Nessun risultato trovato
if (state.customers.isEmpty) {
return const Center(
child: Text(
"Nessun cliente trovato.\nProva a cambiare i termini di ricerca.",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey),
),
);
}
// 3. Mostriamo la lista vera
return ListView.separated(
itemCount: state.customers.length,
separatorBuilder: (context, index) =>
const Divider(height: 1),
itemBuilder: (context, index) {
final customer = state.customers[index];
// Assumo che il tuo CustomerModel abbia le proprietà name e surname.
// Adatta queste variabili al tuo modello reale!
final displayName = customer.name.trim();
return ListTile(
contentPadding: EdgeInsets.zero,
leading: CircleAvatar(
backgroundColor: Theme.of(
context,
).colorScheme.primaryContainer,
foregroundColor: Theme.of(
context,
).colorScheme.onPrimaryContainer,
// Mostra l'iniziale
child: Text(
displayName.isNotEmpty
? displayName[0].toUpperCase()
: "?",
),
),
title: Text(
displayName,
style: const TextStyle(fontWeight: FontWeight.w500),
),
subtitle: Text(customer.email),
trailing: const Icon(
Icons.check_circle_outline,
color: Colors.grey,
),
onTap: () {
// Salviamo l'ID e il nome formattato nel form dei servizi
context.read<OperationsCubit>().updateField(
customerId: customer.id,
customerDisplayName: displayName,
);
// Chiudiamo la modale
Navigator.pop(context);
},
);
},
);
},
),
),
],
),
),
);
}
}

View File

@@ -2,7 +2,7 @@ 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/theme/theme.dart';
import 'package:flux/features/customers/blocs/customer_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/ui/customer_form.dart';
import 'package:go_router/go_router.dart';
@@ -26,14 +26,14 @@ class _CustomersContentState extends State<CustomersContent> {
void _loadInitialCustomers() {
final companyId = context.read<SessionCubit>().state.company?.id;
if (companyId != null) {
context.read<CustomerCubit>().loadCustomers();
context.read<CustomersCubit>().loadCustomers();
}
}
void _onSearch(String query) {
final companyId = context.read<SessionCubit>().state.company?.id;
if (companyId != null) {
context.read<CustomerCubit>().searchCustomers(query);
context.read<CustomersCubit>().searchCustomers(query);
}
}
@@ -86,9 +86,9 @@ class _CustomersContentState extends State<CustomersContent> {
// LISTA CLIENTI
Expanded(
child: BlocBuilder<CustomerCubit, CustomerState>(
child: BlocBuilder<CustomersCubit, CustomersState>(
builder: (context, state) {
if (state.status == CustomerStatus.loading &&
if (state.status == CustomersStatus.loading &&
state.customers.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
@@ -242,12 +242,12 @@ void openCustomerForm({
if (customer == null) {
// CASO NUOVO: Iniettiamo il companyId e inviamo l'evento create
context.read<CustomerCubit>().createCustomer(
context.read<CustomersCubit>().createCustomer(
customerFromForm.copyWith(companyId: companyId),
);
} else {
// CASO MODIFICA: L'ID e il companyId sono già nel modello
context.read<CustomerCubit>().updateCustomer(customerFromForm);
context.read<CustomersCubit>().updateCustomer(customerFromForm);
}
Navigator.pop(dialogContext);
},

View File

@@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/customers/blocs/customer_cubit.dart';
import 'package:flux/features/customers/blocs/customers_cubit.dart';
class QuickCustomerDialog extends StatefulWidget {
final String initialQuery;
@@ -42,13 +42,15 @@ class _QuickCustomerDialogState extends State<QuickCustomerDialog> {
setState(() => _isLoading = true);
// Chiamata al Cubit (aggiorna i parametri in base a come li hai definiti)
final newCustomer = await context.read<CustomerCubit>().quickCreateCustomer(
name: _nameCtrl.text.trim(),
phone: _phoneCtrl.text.trim(),
// Aggiungi questi se li hai inseriti nel tuo CustomerCubit:
// email: _emailCtrl.text.trim(),
// note: _noteCtrl.text.trim(),
);
final newCustomer = await context
.read<CustomersCubit>()
.quickCreateCustomer(
name: _nameCtrl.text.trim(),
phone: _phoneCtrl.text.trim(),
// Aggiungi questi se li hai inseriti nel tuo CustomerCubit:
// email: _emailCtrl.text.trim(),
// note: _noteCtrl.text.trim(),
);
setState(() => _isLoading = false);