rifatta operation form e diverse migliorie generali
This commit is contained in:
@@ -11,7 +11,7 @@ class CustomerFormCubit extends Cubit<CustomerFormState> {
|
||||
final CustomerRepository _repository = GetIt.I<CustomerRepository>();
|
||||
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
||||
|
||||
CustomerFormCubit({CustomerModel? existingCustomer})
|
||||
CustomerFormCubit({CustomerModel? existingCustomer, String? customerId})
|
||||
: super(
|
||||
CustomerFormState(customer: existingCustomer ?? CustomerModel.empty()),
|
||||
);
|
||||
@@ -103,4 +103,26 @@ class CustomerFormCubit extends Cubit<CustomerFormState> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<CustomerModel?> quickCreateCustomer({
|
||||
required String name,
|
||||
String? phone,
|
||||
String? email,
|
||||
}) async {
|
||||
final newCustomer = CustomerModel(
|
||||
name: name,
|
||||
phoneNumber: phone ?? '',
|
||||
email: email ?? '',
|
||||
companyId: _sessionCubit.state.company!.id!,
|
||||
note: '',
|
||||
);
|
||||
|
||||
try {
|
||||
final saved = await _repository.insertCustomer(newCustomer);
|
||||
// Lo aggiungeremo in cima ai suggerimenti
|
||||
return saved;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,160 +0,0 @@
|
||||
import 'dart:async'; // Serve per il Timer del debounce
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
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 'customers_state.dart';
|
||||
|
||||
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;
|
||||
|
||||
CustomersCubit() : super(const CustomersState());
|
||||
|
||||
// --- LETTURA ---
|
||||
Future<void> loadCustomers() async {
|
||||
emit(state.copyWith(status: CustomersStatus.loading));
|
||||
try {
|
||||
final customers = await _repository.getCustomers(
|
||||
_sessionCubit.state.company!.id!,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(status: CustomersStatus.success, customers: customers),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CustomersStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- CREAZIONE ---
|
||||
Future<void> createCustomer(CustomerModel customer) async {
|
||||
emit(state.copyWith(status: CustomersStatus.loading));
|
||||
try {
|
||||
final newCustomer = await _repository.saveCustomer(customer);
|
||||
|
||||
// Aggiorniamo la lista locale aggiungendo il nuovo cliente in cima
|
||||
final updatedList = List<CustomerModel>.from(state.customers)
|
||||
..insert(0, newCustomer);
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CustomersStatus.success,
|
||||
customers: updatedList,
|
||||
lastCreatedCustomer: newCustomer,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CustomersStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- AGGIORNAMENTO ---
|
||||
Future<void> updateCustomer(CustomerModel customer) async {
|
||||
emit(state.copyWith(status: CustomersStatus.loading));
|
||||
try {
|
||||
final updatedCustomer = await _repository.updateCustomer(customer);
|
||||
|
||||
final updatedList = List<CustomerModel>.from(state.customers);
|
||||
final index = updatedList.indexWhere((c) => c.id == updatedCustomer.id);
|
||||
|
||||
if (index != -1) {
|
||||
updatedList[index] = updatedCustomer;
|
||||
}
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CustomersStatus.success,
|
||||
customers: updatedList,
|
||||
lastCreatedCustomer:
|
||||
updatedCustomer, // Utile se modifichi un cliente appena creato
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CustomersStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- RICERCA CON DEBOUNCE ---
|
||||
void searchCustomers(String query) {
|
||||
// 1. Se c'è già una ricerca in attesa (l'utente sta digitando veloce), la annulliamo
|
||||
if (_searchDebounce?.isActive ?? false) _searchDebounce!.cancel();
|
||||
|
||||
// 2. Facciamo partire un timer di 400 millisecondi
|
||||
_searchDebounce = Timer(const Duration(milliseconds: 300), () async {
|
||||
// Se cancella tutto e la query è vuota, ricarichiamo la lista base
|
||||
if (query.trim().isEmpty) {
|
||||
await loadCustomers();
|
||||
return;
|
||||
}
|
||||
|
||||
// Nessun "loading" state qui, per evitare sfarfallii visivi mentre si scrive
|
||||
try {
|
||||
final results = await _repository.searchCustomers(
|
||||
_sessionCubit.state.company!.id!,
|
||||
query,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(status: CustomersStatus.success, customers: results),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CustomersStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<CustomerModel?> quickCreateCustomer({
|
||||
required String name,
|
||||
String? phone,
|
||||
String? email,
|
||||
}) async {
|
||||
final newCustomer = CustomerModel(
|
||||
name: name,
|
||||
phoneNumber: phone ?? '',
|
||||
email: email ?? '',
|
||||
companyId: _sessionCubit.state.company!.id!,
|
||||
note: '',
|
||||
);
|
||||
|
||||
try {
|
||||
final saved = await _repository.saveCustomer(newCustomer);
|
||||
// Lo aggiungiamo in cima ai suggerimenti
|
||||
emit(state.copyWith(customers: [saved, ...state.customers]));
|
||||
return saved;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Pulizia della memoria quando il Cubit viene distrutto
|
||||
@override
|
||||
Future<void> close() {
|
||||
_searchDebounce?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
85
lib/features/customers/blocs/customers_list_cubit.dart
Normal file
85
lib/features/customers/blocs/customers_list_cubit.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
import 'dart:async'; // Serve per il Timer del debounce
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
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 'customers_list_state.dart';
|
||||
|
||||
class CustomersListCubit extends Cubit<CustomersListState> {
|
||||
final CustomerRepository _repository = GetIt.I<CustomerRepository>();
|
||||
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
||||
|
||||
// Variabile per gestire il debounce della ricerca
|
||||
Timer? _searchDebounce;
|
||||
|
||||
CustomersListCubit() : super(const CustomersListState());
|
||||
|
||||
// --- LETTURA ---
|
||||
Future<void> loadCustomers() async {
|
||||
emit(state.copyWith(status: CustomersListStatus.loading));
|
||||
try {
|
||||
final customers = await _repository.getCustomers(
|
||||
_sessionCubit.state.company!.id!,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CustomersListStatus.success,
|
||||
customers: customers,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CustomersListStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// --- RICERCA CON DEBOUNCE ---
|
||||
void searchCustomers(String query) {
|
||||
// 1. Se c'è già una ricerca in attesa (l'utente sta digitando veloce), la annulliamo
|
||||
if (_searchDebounce?.isActive ?? false) _searchDebounce!.cancel();
|
||||
|
||||
// 2. Facciamo partire un timer di 400 millisecondi
|
||||
_searchDebounce = Timer(const Duration(milliseconds: 300), () async {
|
||||
// Se cancella tutto e la query è vuota, ricarichiamo la lista base
|
||||
if (query.trim().isEmpty) {
|
||||
await loadCustomers();
|
||||
return;
|
||||
}
|
||||
|
||||
// Nessun "loading" state qui, per evitare sfarfallii visivi mentre si scrive
|
||||
try {
|
||||
final results = await _repository.searchCustomers(
|
||||
_sessionCubit.state.company!.id!,
|
||||
query,
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CustomersListStatus.success,
|
||||
customers: results,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
status: CustomersListStatus.failure,
|
||||
errorMessage: e.toString(),
|
||||
),
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Pulizia della memoria quando il Cubit viene distrutto
|
||||
@override
|
||||
Future<void> close() {
|
||||
_searchDebounce?.cancel();
|
||||
return super.close();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
part of 'customers_cubit.dart';
|
||||
part of 'customers_list_cubit.dart';
|
||||
|
||||
enum CustomersStatus {
|
||||
enum CustomersListStatus {
|
||||
initial,
|
||||
loading,
|
||||
filesLoading,
|
||||
@@ -9,26 +9,26 @@ enum CustomersStatus {
|
||||
failure,
|
||||
}
|
||||
|
||||
class CustomersState extends Equatable {
|
||||
final CustomersStatus status;
|
||||
class CustomersListState extends Equatable {
|
||||
final CustomersListStatus status;
|
||||
final List<CustomerModel> customers;
|
||||
final CustomerModel? lastCreatedCustomer;
|
||||
final String? errorMessage;
|
||||
|
||||
const CustomersState({
|
||||
this.status = CustomersStatus.initial,
|
||||
const CustomersListState({
|
||||
this.status = CustomersListStatus.initial,
|
||||
this.customers = const [],
|
||||
this.lastCreatedCustomer,
|
||||
this.errorMessage,
|
||||
});
|
||||
|
||||
CustomersState copyWith({
|
||||
CustomersStatus? status,
|
||||
CustomersListState copyWith({
|
||||
CustomersListStatus? status,
|
||||
List<CustomerModel>? customers,
|
||||
CustomerModel? lastCreatedCustomer,
|
||||
String? errorMessage,
|
||||
}) {
|
||||
return CustomersState(
|
||||
return CustomersListState(
|
||||
status: status ?? this.status,
|
||||
customers: customers ?? this.customers,
|
||||
lastCreatedCustomer: lastCreatedCustomer ?? this.lastCreatedCustomer,
|
||||
@@ -1,20 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/widgets/flux_text_field.dart';
|
||||
import 'package:flux/core/widgets/shared_forms/attachments_section.dart';
|
||||
import 'package:flux/features/attachments/blocs/attachments_bloc.dart';
|
||||
import 'package:flux/features/customers/blocs/customer_form_cubit.dart';
|
||||
import 'package:flux/features/customers/models/customer_model.dart'; // Uso il tuo widget!
|
||||
|
||||
class CustomerForm extends StatefulWidget {
|
||||
class CustomerFormScreen extends StatefulWidget {
|
||||
final CustomerModel? customer;
|
||||
final String? customerId;
|
||||
|
||||
const CustomerForm({super.key, this.customer, this.customerId});
|
||||
const CustomerFormScreen({super.key, this.customer, this.customerId});
|
||||
|
||||
@override
|
||||
State<CustomerForm> createState() => _CustomerFormState();
|
||||
State<CustomerFormScreen> createState() => _CustomerFormScreenState();
|
||||
}
|
||||
|
||||
class _CustomerFormState extends State<CustomerForm> {
|
||||
class _CustomerFormScreenState extends State<CustomerFormScreen> {
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
// Controller inizializzati con i dati del cliente (se presenti)
|
||||
@@ -27,10 +29,18 @@ class _CustomerFormState extends State<CustomerForm> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
// 1. Lanciamo l'inizializzazione (che se è sincrona, finirà istantaneamente)
|
||||
context.read<CustomerFormCubit>().initForm(
|
||||
customerId: widget.customerId,
|
||||
existingCustomer: widget.customer,
|
||||
);
|
||||
|
||||
// 2. Leggiamo lo stato SUBITO DOPO. Se è già ready, non aspettiamo il listener!
|
||||
final currentState = context.read<CustomerFormCubit>().state;
|
||||
if (currentState.status == CustomerFormStatus.ready && !_isInitialized) {
|
||||
_syncTextControllers(currentState.customer);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -144,6 +154,17 @@ class _CustomerFormState extends State<CustomerForm> {
|
||||
.read<CustomerFormCubit>()
|
||||
.updateDoNotDisturb(v),
|
||||
),
|
||||
const Divider(height: 32),
|
||||
BlocProvider<AttachmentsBloc>(
|
||||
create: (context) => AttachmentsBloc(
|
||||
parentType: AttachmentParentType.customer,
|
||||
parentId: state.customer.id,
|
||||
),
|
||||
child: SharedAttachmentsSection(
|
||||
parentType: AttachmentParentType.customer,
|
||||
parentId: state.customer.id,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
SizedBox(
|
||||
width: double.infinity,
|
||||
@@ -3,9 +3,8 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/blocs/session/session_cubit.dart';
|
||||
import 'package:flux/core/routes/routes.dart';
|
||||
import 'package:flux/core/theme/theme.dart';
|
||||
import 'package:flux/features/customers/blocs/customers_cubit.dart';
|
||||
import 'package:flux/features/customers/blocs/customers_list_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';
|
||||
|
||||
class CustomersListScreen extends StatefulWidget {
|
||||
@@ -27,14 +26,14 @@ class _CustomersListScreenState extends State<CustomersListScreen> {
|
||||
void _loadInitialCustomers() {
|
||||
final companyId = context.read<SessionCubit>().state.company?.id;
|
||||
if (companyId != null) {
|
||||
context.read<CustomersCubit>().loadCustomers();
|
||||
context.read<CustomersListCubit>().loadCustomers();
|
||||
}
|
||||
}
|
||||
|
||||
void _onSearch(String query) {
|
||||
final companyId = context.read<SessionCubit>().state.company?.id;
|
||||
if (companyId != null) {
|
||||
context.read<CustomersCubit>().searchCustomers(query);
|
||||
context.read<CustomersListCubit>().searchCustomers(query);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +52,12 @@ class _CustomersListScreenState extends State<CustomersListScreen> {
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16),
|
||||
child: ElevatedButton.icon(
|
||||
onPressed: () => openCustomerForm(context: context),
|
||||
onPressed: () {
|
||||
context.pushNamed(
|
||||
Routes.customerForm,
|
||||
pathParameters: {'id': 'new'},
|
||||
);
|
||||
},
|
||||
icon: const Icon(Icons.person_add_alt_1_rounded, size: 20),
|
||||
label: const Text('NUOVO'),
|
||||
style: ElevatedButton.styleFrom(
|
||||
@@ -87,9 +91,9 @@ class _CustomersListScreenState extends State<CustomersListScreen> {
|
||||
|
||||
// LISTA CLIENTI
|
||||
Expanded(
|
||||
child: BlocBuilder<CustomersCubit, CustomersState>(
|
||||
child: BlocBuilder<CustomersListCubit, CustomersListState>(
|
||||
builder: (context, state) {
|
||||
if (state.status == CustomersStatus.loading &&
|
||||
if (state.status == CustomersListStatus.loading &&
|
||||
state.customers.isEmpty) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
@@ -214,8 +218,16 @@ class _CustomerTile extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
trailing: IconButton(
|
||||
onPressed: () =>
|
||||
openCustomerForm(context: context, customer: customer),
|
||||
onPressed: () async {
|
||||
final CustomersListCubit customersCubit = context
|
||||
.read<CustomersListCubit>();
|
||||
await context.pushNamed(
|
||||
Routes.customerForm,
|
||||
pathParameters: {'id': customer.id!},
|
||||
extra: customer,
|
||||
);
|
||||
customersCubit.loadCustomers();
|
||||
},
|
||||
icon: Icon(Icons.edit_note_rounded, color: context.accent),
|
||||
),
|
||||
),
|
||||
@@ -224,7 +236,7 @@ class _CustomerTile extends StatelessWidget {
|
||||
}
|
||||
|
||||
/// Funzione unica per gestire Creazione e Modifica
|
||||
void openCustomerForm({
|
||||
/* void openCustomerForm({
|
||||
CustomerModel? customer,
|
||||
required BuildContext context,
|
||||
}) {
|
||||
@@ -257,4 +269,4 @@ void openCustomerForm({
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} */
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/features/customers/blocs/customers_cubit.dart';
|
||||
import 'package:flux/features/customers/blocs/customer_form_cubit.dart';
|
||||
|
||||
class QuickCustomerDialog extends StatefulWidget {
|
||||
final String initialQuery;
|
||||
@@ -43,13 +43,11 @@ class _QuickCustomerDialogState extends State<QuickCustomerDialog> {
|
||||
|
||||
// Chiamata al Cubit (aggiorna i parametri in base a come li hai definiti)
|
||||
final newCustomer = await context
|
||||
.read<CustomersCubit>()
|
||||
.read<CustomerFormCubit>()
|
||||
.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(),
|
||||
email: _emailCtrl.text.trim(),
|
||||
);
|
||||
|
||||
setState(() => _isLoading = false);
|
||||
|
||||
Reference in New Issue
Block a user