rifatta operation form e diverse migliorie generali
This commit is contained in:
@@ -12,10 +12,11 @@ import 'package:flux/core/widgets/image_upload/ui/upload_success_screen.dart';
|
|||||||
import 'package:flux/features/auth/ui/auth_screen.dart';
|
import 'package:flux/features/auth/ui/auth_screen.dart';
|
||||||
import 'package:flux/features/company/bloc/company_settings_cubit.dart';
|
import 'package:flux/features/company/bloc/company_settings_cubit.dart';
|
||||||
import 'package:flux/features/company/ui/company_settings_screen.dart';
|
import 'package:flux/features/company/ui/company_settings_screen.dart';
|
||||||
import 'package:flux/features/customers/blocs/customers_cubit.dart';
|
import 'package:flux/features/customers/blocs/customer_form_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/models/customer_model.dart';
|
||||||
import 'package:flux/features/customers/ui/customer_detail_screen.dart';
|
import 'package:flux/features/customers/ui/customer_detail_screen.dart';
|
||||||
import 'package:flux/features/customers/ui/customer_form.dart';
|
import 'package:flux/features/customers/ui/customer_form_screen.dart';
|
||||||
import 'package:flux/features/customers/ui/customers_list_screen.dart';
|
import 'package:flux/features/customers/ui/customers_list_screen.dart';
|
||||||
import 'package:flux/features/home/ui/home_screen.dart';
|
import 'package:flux/features/home/ui/home_screen.dart';
|
||||||
import 'package:flux/features/master_data/master_data_hub_content.dart';
|
import 'package:flux/features/master_data/master_data_hub_content.dart';
|
||||||
@@ -247,7 +248,7 @@ class AppRouter {
|
|||||||
TrackingParentType.ticket,
|
TrackingParentType.ticket,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
context.read<CustomersCubit>().loadCustomers();
|
context.read<CustomersListCubit>().loadCustomers();
|
||||||
context.read<ProductsCubit>().loadModels();
|
context.read<ProductsCubit>().loadModels();
|
||||||
context.read<ProductsCubit>().loadBrands();
|
context.read<ProductsCubit>().loadBrands();
|
||||||
|
|
||||||
@@ -328,13 +329,24 @@ class AppRouter {
|
|||||||
path: '/customer/form/:id',
|
path: '/customer/form/:id',
|
||||||
name: Routes.customerForm,
|
name: Routes.customerForm,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final String pathId = state.pathParameters['id'] ?? 'new';
|
||||||
|
final String? realCustomerId;
|
||||||
|
if (pathId == 'new') {
|
||||||
|
realCustomerId = null;
|
||||||
|
} else {
|
||||||
|
realCustomerId = pathId;
|
||||||
|
}
|
||||||
final customer = state.extra as CustomerModel?;
|
final customer = state.extra as CustomerModel?;
|
||||||
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => AttachmentsBloc(
|
create: (context) => CustomerFormCubit(
|
||||||
parentType: AttachmentParentType.customer,
|
existingCustomer: customer,
|
||||||
parentId: customer.id,
|
customerId: realCustomerId,
|
||||||
|
),
|
||||||
|
child: CustomerFormScreen(
|
||||||
|
customer: customer,
|
||||||
|
customerId: realCustomerId,
|
||||||
),
|
),
|
||||||
child: CustomerForm(customer: customer),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -365,7 +377,7 @@ class AppRouter {
|
|||||||
.state
|
.state
|
||||||
.currentStore!
|
.currentStore!
|
||||||
.id!;
|
.id!;
|
||||||
context.read<CustomersCubit>().loadCustomers();
|
context.read<CustomersListCubit>().loadCustomers();
|
||||||
context.read<ProviderListCubit>().loadProviders(currentStoreId);
|
context.read<ProviderListCubit>().loadProviders(currentStoreId);
|
||||||
context.read<ProductsCubit>().loadModels();
|
context.read<ProductsCubit>().loadModels();
|
||||||
context.read<ProductsCubit>().loadBrands();
|
context.read<ProductsCubit>().loadBrands();
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
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/utils/extensions.dart';
|
import 'package:flux/core/utils/extensions.dart';
|
||||||
import 'package:flux/core/widgets/flux_text_field.dart';
|
import 'package:flux/core/widgets/flux_text_field.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
@@ -72,6 +74,12 @@ class _SetPasswordScreenState extends State<SetPasswordScreen> {
|
|||||||
title: Text(context.l10n.setPasswordScreenWelcomeInFlux),
|
title: Text(context.l10n.setPasswordScreenWelcomeInFlux),
|
||||||
automaticallyImplyLeading:
|
automaticallyImplyLeading:
|
||||||
false, // Non può tornare indietro, deve mettere la password!
|
false, // Non può tornare indietro, deve mettere la password!
|
||||||
|
actions: [
|
||||||
|
IconButton.filled(
|
||||||
|
onPressed: () => context.read<SessionCubit>().signOut(),
|
||||||
|
icon: Icon(Icons.logout),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: Padding(
|
body: Padding(
|
||||||
padding: const EdgeInsets.all(24.0),
|
padding: const EdgeInsets.all(24.0),
|
||||||
|
|||||||
@@ -426,7 +426,7 @@ class _SharedAttachmentsSectionState extends State<SharedAttachmentsSection> {
|
|||||||
color: theme.colorScheme.primary,
|
color: theme.colorScheme.primary,
|
||||||
),
|
),
|
||||||
title: const Text(
|
title: const Text(
|
||||||
'Cartella Export (Es. TIM AttachmentRepository)',
|
'Cartella Export PDF',
|
||||||
style: TextStyle(fontWeight: FontWeight.bold),
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flux/core/routes/routes.dart';
|
import 'package:flux/core/routes/routes.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/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:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
@@ -253,7 +253,7 @@ class SharedCustomerSection extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
onChanged: (query) {
|
onChanged: (query) {
|
||||||
currentSearchQuery = query;
|
currentSearchQuery = query;
|
||||||
context.read<CustomersCubit>().searchCustomers(query);
|
context.read<CustomersListCubit>().searchCustomers(query);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -272,7 +272,7 @@ class SharedCustomerSection extends StatelessWidget {
|
|||||||
context: context,
|
context: context,
|
||||||
builder: (dialogContext) {
|
builder: (dialogContext) {
|
||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: context.read<CustomersCubit>(),
|
value: context.read<CustomersListCubit>(),
|
||||||
child: QuickCustomerDialog(
|
child: QuickCustomerDialog(
|
||||||
initialQuery:
|
initialQuery:
|
||||||
currentSearchQuery, // <-- Passiamo quello che ha digitato!
|
currentSearchQuery, // <-- Passiamo quello che ha digitato!
|
||||||
@@ -297,9 +297,9 @@ class SharedCustomerSection extends StatelessWidget {
|
|||||||
const Divider(),
|
const Divider(),
|
||||||
// Lista Clienti dal Bloc
|
// Lista Clienti dal Bloc
|
||||||
Expanded(
|
Expanded(
|
||||||
child: BlocBuilder<CustomersCubit, CustomersState>(
|
child: BlocBuilder<CustomersListCubit, CustomersListState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.status == CustomersStatus.loading) {
|
if (state.status == CustomersListStatus.loading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
if (state.customers.isEmpty) {
|
if (state.customers.isEmpty) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ class CustomerFormCubit extends Cubit<CustomerFormState> {
|
|||||||
final CustomerRepository _repository = GetIt.I<CustomerRepository>();
|
final CustomerRepository _repository = GetIt.I<CustomerRepository>();
|
||||||
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
final SessionCubit _sessionCubit = GetIt.I<SessionCubit>();
|
||||||
|
|
||||||
CustomerFormCubit({CustomerModel? existingCustomer})
|
CustomerFormCubit({CustomerModel? existingCustomer, String? customerId})
|
||||||
: super(
|
: super(
|
||||||
CustomerFormState(customer: existingCustomer ?? CustomerModel.empty()),
|
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,
|
initial,
|
||||||
loading,
|
loading,
|
||||||
filesLoading,
|
filesLoading,
|
||||||
@@ -9,26 +9,26 @@ enum CustomersStatus {
|
|||||||
failure,
|
failure,
|
||||||
}
|
}
|
||||||
|
|
||||||
class CustomersState extends Equatable {
|
class CustomersListState extends Equatable {
|
||||||
final CustomersStatus status;
|
final CustomersListStatus status;
|
||||||
final List<CustomerModel> customers;
|
final List<CustomerModel> customers;
|
||||||
final CustomerModel? lastCreatedCustomer;
|
final CustomerModel? lastCreatedCustomer;
|
||||||
final String? errorMessage;
|
final String? errorMessage;
|
||||||
|
|
||||||
const CustomersState({
|
const CustomersListState({
|
||||||
this.status = CustomersStatus.initial,
|
this.status = CustomersListStatus.initial,
|
||||||
this.customers = const [],
|
this.customers = const [],
|
||||||
this.lastCreatedCustomer,
|
this.lastCreatedCustomer,
|
||||||
this.errorMessage,
|
this.errorMessage,
|
||||||
});
|
});
|
||||||
|
|
||||||
CustomersState copyWith({
|
CustomersListState copyWith({
|
||||||
CustomersStatus? status,
|
CustomersListStatus? status,
|
||||||
List<CustomerModel>? customers,
|
List<CustomerModel>? customers,
|
||||||
CustomerModel? lastCreatedCustomer,
|
CustomerModel? lastCreatedCustomer,
|
||||||
String? errorMessage,
|
String? errorMessage,
|
||||||
}) {
|
}) {
|
||||||
return CustomersState(
|
return CustomersListState(
|
||||||
status: status ?? this.status,
|
status: status ?? this.status,
|
||||||
customers: customers ?? this.customers,
|
customers: customers ?? this.customers,
|
||||||
lastCreatedCustomer: lastCreatedCustomer ?? this.lastCreatedCustomer,
|
lastCreatedCustomer: lastCreatedCustomer ?? this.lastCreatedCustomer,
|
||||||
@@ -1,20 +1,22 @@
|
|||||||
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/widgets/flux_text_field.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/blocs/customer_form_cubit.dart';
|
||||||
import 'package:flux/features/customers/models/customer_model.dart'; // Uso il tuo widget!
|
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 CustomerModel? customer;
|
||||||
final String? customerId;
|
final String? customerId;
|
||||||
|
|
||||||
const CustomerForm({super.key, this.customer, this.customerId});
|
const CustomerFormScreen({super.key, this.customer, this.customerId});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CustomerForm> createState() => _CustomerFormState();
|
State<CustomerFormScreen> createState() => _CustomerFormScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CustomerFormState extends State<CustomerForm> {
|
class _CustomerFormScreenState extends State<CustomerFormScreen> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
// Controller inizializzati con i dati del cliente (se presenti)
|
// Controller inizializzati con i dati del cliente (se presenti)
|
||||||
@@ -27,10 +29,18 @@ class _CustomerFormState extends State<CustomerForm> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
|
// 1. Lanciamo l'inizializzazione (che se è sincrona, finirà istantaneamente)
|
||||||
context.read<CustomerFormCubit>().initForm(
|
context.read<CustomerFormCubit>().initForm(
|
||||||
customerId: widget.customerId,
|
customerId: widget.customerId,
|
||||||
existingCustomer: widget.customer,
|
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
|
@override
|
||||||
@@ -144,6 +154,17 @@ class _CustomerFormState extends State<CustomerForm> {
|
|||||||
.read<CustomerFormCubit>()
|
.read<CustomerFormCubit>()
|
||||||
.updateDoNotDisturb(v),
|
.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),
|
const SizedBox(height: 24),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: double.infinity,
|
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/blocs/session/session_cubit.dart';
|
||||||
import 'package:flux/core/routes/routes.dart';
|
import 'package:flux/core/routes/routes.dart';
|
||||||
import 'package:flux/core/theme/theme.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/models/customer_model.dart';
|
||||||
import 'package:flux/features/customers/ui/customer_form.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
class CustomersListScreen extends StatefulWidget {
|
class CustomersListScreen extends StatefulWidget {
|
||||||
@@ -27,14 +26,14 @@ class _CustomersListScreenState extends State<CustomersListScreen> {
|
|||||||
void _loadInitialCustomers() {
|
void _loadInitialCustomers() {
|
||||||
final companyId = context.read<SessionCubit>().state.company?.id;
|
final companyId = context.read<SessionCubit>().state.company?.id;
|
||||||
if (companyId != null) {
|
if (companyId != null) {
|
||||||
context.read<CustomersCubit>().loadCustomers();
|
context.read<CustomersListCubit>().loadCustomers();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSearch(String query) {
|
void _onSearch(String query) {
|
||||||
final companyId = context.read<SessionCubit>().state.company?.id;
|
final companyId = context.read<SessionCubit>().state.company?.id;
|
||||||
if (companyId != null) {
|
if (companyId != null) {
|
||||||
context.read<CustomersCubit>().searchCustomers(query);
|
context.read<CustomersListCubit>().searchCustomers(query);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -53,7 +52,12 @@ class _CustomersListScreenState extends State<CustomersListScreen> {
|
|||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.only(right: 16),
|
padding: const EdgeInsets.only(right: 16),
|
||||||
child: ElevatedButton.icon(
|
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),
|
icon: const Icon(Icons.person_add_alt_1_rounded, size: 20),
|
||||||
label: const Text('NUOVO'),
|
label: const Text('NUOVO'),
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
@@ -87,9 +91,9 @@ class _CustomersListScreenState extends State<CustomersListScreen> {
|
|||||||
|
|
||||||
// LISTA CLIENTI
|
// LISTA CLIENTI
|
||||||
Expanded(
|
Expanded(
|
||||||
child: BlocBuilder<CustomersCubit, CustomersState>(
|
child: BlocBuilder<CustomersListCubit, CustomersListState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
if (state.status == CustomersStatus.loading &&
|
if (state.status == CustomersListStatus.loading &&
|
||||||
state.customers.isEmpty) {
|
state.customers.isEmpty) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator());
|
||||||
}
|
}
|
||||||
@@ -214,8 +218,16 @@ class _CustomerTile extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
onPressed: () =>
|
onPressed: () async {
|
||||||
openCustomerForm(context: context, customer: customer),
|
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),
|
icon: Icon(Icons.edit_note_rounded, color: context.accent),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -224,7 +236,7 @@ class _CustomerTile extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Funzione unica per gestire Creazione e Modifica
|
/// Funzione unica per gestire Creazione e Modifica
|
||||||
void openCustomerForm({
|
/* void openCustomerForm({
|
||||||
CustomerModel? customer,
|
CustomerModel? customer,
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
}) {
|
}) {
|
||||||
@@ -257,4 +269,4 @@ void openCustomerForm({
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
} */
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
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/features/customers/blocs/customers_cubit.dart';
|
import 'package:flux/features/customers/blocs/customer_form_cubit.dart';
|
||||||
|
|
||||||
class QuickCustomerDialog extends StatefulWidget {
|
class QuickCustomerDialog extends StatefulWidget {
|
||||||
final String initialQuery;
|
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)
|
// Chiamata al Cubit (aggiorna i parametri in base a come li hai definiti)
|
||||||
final newCustomer = await context
|
final newCustomer = await context
|
||||||
.read<CustomersCubit>()
|
.read<CustomerFormCubit>()
|
||||||
.quickCreateCustomer(
|
.quickCreateCustomer(
|
||||||
name: _nameCtrl.text.trim(),
|
name: _nameCtrl.text.trim(),
|
||||||
phone: _phoneCtrl.text.trim(),
|
phone: _phoneCtrl.text.trim(),
|
||||||
// Aggiungi questi se li hai inseriti nel tuo CustomerCubit:
|
email: _emailCtrl.text.trim(),
|
||||||
// email: _emailCtrl.text.trim(),
|
|
||||||
// note: _noteCtrl.text.trim(),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
setState(() => _isLoading = false);
|
setState(() => _isLoading = false);
|
||||||
|
|||||||
@@ -84,6 +84,14 @@ class _OnboardingScreenState extends State<OnboardingScreen> {
|
|||||||
},
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
actions: [
|
||||||
|
IconButton.filled(
|
||||||
|
onPressed: () => context.read<SessionCubit>().signOut(),
|
||||||
|
icon: const Icon(Icons.logout),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
body: SafeArea(
|
body: SafeArea(
|
||||||
child: Stack(
|
child: Stack(
|
||||||
children: [
|
children: [
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import 'package:flux/features/operations/blocs/operation_form_cubit.dart';
|
|||||||
import 'package:flux/features/operations/models/operation_model.dart';
|
import 'package:flux/features/operations/models/operation_model.dart';
|
||||||
import 'package:flux/core/widgets/shared_forms/customer_section.dart';
|
import 'package:flux/core/widgets/shared_forms/customer_section.dart';
|
||||||
import 'package:flux/features/operations/ui/widgets/details_section.dart';
|
import 'package:flux/features/operations/ui/widgets/details_section.dart';
|
||||||
import 'package:flux/core/widgets/shared_forms/attachments_section.dart';
|
import 'package:flux/core/widgets/shared_forms/shared_files_section.dart'; // <- Cambiato ad un file unico per coerenza col ticket
|
||||||
import 'package:flux/core/widgets/shared_forms/staff_section.dart';
|
|
||||||
|
|
||||||
class OperationFormScreen extends StatefulWidget {
|
class OperationFormScreen extends StatefulWidget {
|
||||||
final String? operationId;
|
final String? operationId;
|
||||||
@@ -35,6 +34,7 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
'MNP',
|
'MNP',
|
||||||
'NIP',
|
'NIP',
|
||||||
'UNICA',
|
'UNICA',
|
||||||
|
'FWA',
|
||||||
'TELEPASS',
|
'TELEPASS',
|
||||||
'Energy',
|
'Energy',
|
||||||
'Fin',
|
'Fin',
|
||||||
@@ -47,10 +47,17 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
// 1. Lanciamo l'inizializzazione sincrona/asincrona
|
||||||
context.read<OperationFormCubit>().initForm(
|
context.read<OperationFormCubit>().initForm(
|
||||||
existingOperation: widget.existingOperation,
|
existingOperation: widget.existingOperation,
|
||||||
operationId: widget.operationId,
|
operationId: widget.operationId,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 2. Lettura immediata dello stato (come fatto per il customer!)
|
||||||
|
final currentState = context.read<OperationFormCubit>().state;
|
||||||
|
if (currentState.status == OperationFormStatus.ready && !_isInitialized) {
|
||||||
|
_syncTextControllers(currentState.operation);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -75,16 +82,6 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
if (_freeTextDescriptionController.text.isEmpty) {
|
if (_freeTextDescriptionController.text.isEmpty) {
|
||||||
_freeTextDescriptionController.text = model.description ?? '';
|
_freeTextDescriptionController.text = model.description ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se è una nuova pratica (draft), impostiamo di default il target su OK per comodità UI
|
|
||||||
if (model.id == null && model.status == OperationStatus.draft) {
|
|
||||||
// Usiamo addPostFrameCallback per non interferire con il build attuale
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
||||||
// Supponendo tu aggiunga la possibilità di aggiornare lo status nel metodo updateFields del Cubit
|
|
||||||
// context.read<OperationFormCubit>().updateFields(status: OperationStatus.ok);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
_isInitialized = true;
|
_isInitialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,6 +100,9 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
}) {
|
}) {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
_flushControllersToCubit();
|
_flushControllersToCubit();
|
||||||
|
// Aggiorniamo prima lo stato bersaglio nel cubit
|
||||||
|
context.read<OperationFormCubit>().updateFields(status: targetStatus);
|
||||||
|
// Poi chiamiamo il salvataggio
|
||||||
context.read<OperationFormCubit>().saveOperation(
|
context.read<OperationFormCubit>().saveOperation(
|
||||||
targetStatus: targetStatus,
|
targetStatus: targetStatus,
|
||||||
keepAdding: keepAdding,
|
keepAdding: keepAdding,
|
||||||
@@ -114,7 +114,8 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
if (!_formKey.currentState!.validate()) return null;
|
if (!_formKey.currentState!.validate()) return null;
|
||||||
_flushControllersToCubit();
|
_flushControllersToCubit();
|
||||||
final attachmentsBloc = context.read<AttachmentsBloc>();
|
final attachmentsBloc = context.read<AttachmentsBloc>();
|
||||||
// Presumo tu abbia creato il metodo saveOperationDraft() nel Cubit!
|
|
||||||
|
// Assicurati che questo metodo esista nel Cubit (come per il Ticket)
|
||||||
final newId = await context.read<OperationFormCubit>().saveOperationDraft();
|
final newId = await context.read<OperationFormCubit>().saveOperationDraft();
|
||||||
if (newId != null && context.mounted) {
|
if (newId != null && context.mounted) {
|
||||||
attachmentsBloc.add(ParentEntitySavedEvent(newId));
|
attachmentsBloc.add(ParentEntitySavedEvent(newId));
|
||||||
@@ -122,7 +123,6 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
return newId;
|
return newId;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper per assegnare un colore agli stati
|
|
||||||
Color _getStatusColor(OperationStatus status) {
|
Color _getStatusColor(OperationStatus status) {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case OperationStatus.success:
|
case OperationStatus.success:
|
||||||
@@ -158,6 +158,7 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
);
|
);
|
||||||
_freeTextSubtypeController.clear();
|
_freeTextSubtypeController.clear();
|
||||||
_freeTextDescriptionController.clear();
|
_freeTextDescriptionController.clear();
|
||||||
|
_referenceController.clear();
|
||||||
} else if (state.status == OperationFormStatus.failure) {
|
} else if (state.status == OperationFormStatus.failure) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
SnackBar(
|
SnackBar(
|
||||||
@@ -176,8 +177,6 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determiniamo lo stato da mostrare nel form.
|
|
||||||
// Se è una bozza appena creata, mostriamo visivamente "OK" come default per il salvataggio.
|
|
||||||
final displayStatus =
|
final displayStatus =
|
||||||
state.operation.status == OperationStatus.draft &&
|
state.operation.status == OperationStatus.draft &&
|
||||||
state.operation.id == null
|
state.operation.id == null
|
||||||
@@ -191,7 +190,6 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
? 'Nuova Pratica - Operatore: ${state.operation.staffDisplayName}'
|
? 'Nuova Pratica - Operatore: ${state.operation.staffDisplayName}'
|
||||||
: 'Modifica Pratica - Operatore: ${state.operation.staffDisplayName}',
|
: 'Modifica Pratica - Operatore: ${state.operation.staffDisplayName}',
|
||||||
),
|
),
|
||||||
// Mettiamo un piccolo indicatore visivo anche nella AppBar se non è OK
|
|
||||||
actions:
|
actions:
|
||||||
displayStatus != OperationStatus.success &&
|
displayStatus != OperationStatus.success &&
|
||||||
displayStatus != OperationStatus.draft
|
displayStatus != OperationStatus.draft
|
||||||
@@ -214,64 +212,54 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
),
|
),
|
||||||
body: Form(
|
body: Form(
|
||||||
key: _formKey,
|
key: _formKey,
|
||||||
|
child: FocusTraversalGroup(
|
||||||
|
policy: WidgetOrderTraversalPolicy(),
|
||||||
child: LayoutBuilder(
|
child: LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final isUltraWide = constraints.maxWidth > 1400;
|
final isUltraWide = constraints.maxWidth > 1400;
|
||||||
final isDesktop = constraints.maxWidth > 900;
|
final isDesktop = constraints.maxWidth > 900;
|
||||||
if (isUltraWide) {
|
|
||||||
return _buildUltraWide(state, theme);
|
return SingleChildScrollView(
|
||||||
} else if (isDesktop) {
|
|
||||||
return Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
flex: 7,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
child: _buildMainFormContent(
|
child: Center(
|
||||||
theme,
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: isUltraWide
|
||||||
|
? 1600
|
||||||
|
: (isDesktop ? 1200 : 800),
|
||||||
|
),
|
||||||
|
child: _buildResponsiveLayout(
|
||||||
|
isUltraWide,
|
||||||
|
isDesktop,
|
||||||
state,
|
state,
|
||||||
displayStatus,
|
displayStatus,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
VerticalDivider(width: 1, color: theme.dividerColor),
|
|
||||||
Expanded(
|
|
||||||
flex: 3,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: _buildNotesSection(isDesktop: true),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
} else {
|
|
||||||
return SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
_buildMainFormContent(theme, state, displayStatus),
|
|
||||||
const Divider(height: 32),
|
|
||||||
_buildNotesSection(isDesktop: false),
|
|
||||||
const SizedBox(height: 80),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
bottomNavigationBar: SafeArea(
|
bottomNavigationBar: SafeArea(
|
||||||
child: Padding(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(16.0),
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: theme.scaffoldBackgroundColor,
|
||||||
|
boxShadow: [
|
||||||
|
BoxShadow(
|
||||||
|
color: Colors.black.withValues(alpha: 0.05),
|
||||||
|
blurRadius: 10,
|
||||||
|
offset: const Offset(0, -3),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: ElevatedButton(
|
child: ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
// Se c'è un KO o un blocco, cambiamo il colore del bottone principale per attirare l'attenzione
|
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
displayStatus != OperationStatus.success &&
|
displayStatus != OperationStatus.success &&
|
||||||
displayStatus != OperationStatus.draft
|
displayStatus != OperationStatus.draft
|
||||||
@@ -287,8 +275,7 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
? null
|
? null
|
||||||
: () => _saveOperation(
|
: () => _saveOperation(
|
||||||
keepAdding: false,
|
keepAdding: false,
|
||||||
targetStatus:
|
targetStatus: displayStatus,
|
||||||
displayStatus, // <-- Usiamo lo stato selezionato nel form!
|
|
||||||
),
|
),
|
||||||
child: state.status == OperationFormStatus.saving
|
child: state.status == OperationFormStatus.saving
|
||||||
? const SizedBox(
|
? const SizedBox(
|
||||||
@@ -302,7 +289,6 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
: const Text('Salva ed Esci'),
|
: const Text('Salva ed Esci'),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
||||||
const SizedBox(width: 12),
|
const SizedBox(width: 12),
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -311,8 +297,7 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
? null
|
? null
|
||||||
: () => _saveOperation(
|
: () => _saveOperation(
|
||||||
keepAdding: true,
|
keepAdding: true,
|
||||||
targetStatus:
|
targetStatus: displayStatus,
|
||||||
displayStatus, // <-- Usiamo lo stato selezionato nel form!
|
|
||||||
),
|
),
|
||||||
child: const Text(
|
child: const Text(
|
||||||
'Salva e Aggiungi Altro',
|
'Salva e Aggiungi Altro',
|
||||||
@@ -329,72 +314,100 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildUltraWide(OperationFormState state, ThemeData theme) {
|
// --- LOGICA DI IMPAGINAZIONE RESPONSIVE ---
|
||||||
|
Widget _buildResponsiveLayout(
|
||||||
|
bool isUltraWide,
|
||||||
|
bool isDesktop,
|
||||||
|
OperationFormState state,
|
||||||
|
OperationStatus displayStatus,
|
||||||
|
) {
|
||||||
|
if (isUltraWide) {
|
||||||
|
// 3 COLONNE
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 4,
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
child: Column(
|
||||||
|
children: [_cardAnagrafica(state), _cardEsito(state)],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 24),
|
||||||
|
Expanded(child: Column(children: [_cardDettagli(state)])),
|
||||||
|
const SizedBox(width: 24),
|
||||||
|
Expanded(
|
||||||
|
child: Column(children: [_cardNote(state), _cardAllegati(state)]),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
} else if (isDesktop) {
|
||||||
|
// 2 COLONNE
|
||||||
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
_buildStaffSection(state),
|
|
||||||
const Divider(height: 50),
|
|
||||||
_buildOperationStatusSection(state),
|
|
||||||
const Divider(height: 32),
|
|
||||||
_buildCustomerSection(state),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_buildReferenceSection(state),
|
|
||||||
const Divider(height: 50),
|
|
||||||
_buildOperationTypeSection(state),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
_buildQuantitySection(state),
|
|
||||||
const Divider(height: 50),
|
|
||||||
_buildDetailsSection(state),
|
|
||||||
const Divider(height: 50),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
VerticalDivider(width: 1, color: theme.dividerColor),
|
|
||||||
Expanded(
|
Expanded(
|
||||||
flex: 3,
|
child: Column(
|
||||||
child: Padding(
|
children: [
|
||||||
padding: const EdgeInsets.all(16.0),
|
_cardAnagrafica(state),
|
||||||
child: _buildNotesSection(isDesktop: true),
|
_cardEsito(state),
|
||||||
),
|
_cardAllegati(state),
|
||||||
),
|
],
|
||||||
VerticalDivider(width: 1, color: theme.dividerColor),
|
),
|
||||||
Expanded(
|
),
|
||||||
flex: 3,
|
const SizedBox(width: 24),
|
||||||
child: SingleChildScrollView(
|
Expanded(
|
||||||
padding: const EdgeInsets.all(16.0),
|
child: Column(children: [_cardDettagli(state), _cardNote(state)]),
|
||||||
child: _buildAttachmentSection(state),
|
),
|
||||||
),
|
],
|
||||||
),
|
);
|
||||||
],
|
} else {
|
||||||
);
|
// 1 COLONNA (Mobile)
|
||||||
}
|
return Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
Widget _buildStaffSection(OperationFormState state) {
|
children: [
|
||||||
return StaffSection(
|
_cardAnagrafica(state),
|
||||||
staffId: state.operation.staffId,
|
_cardEsito(state),
|
||||||
staffName: state.operation.staffDisplayName,
|
_cardDettagli(state),
|
||||||
onStaffSelected: (staff) => {
|
_cardNote(state),
|
||||||
context.read<OperationFormCubit>().updateFields(
|
_cardAllegati(state),
|
||||||
staffId: staff.id,
|
],
|
||||||
staffDisplayName: staff.name,
|
);
|
||||||
),
|
}
|
||||||
},
|
}
|
||||||
);
|
|
||||||
}
|
// --- LE CARD MODULARIZZATE E COLORATE ---
|
||||||
|
|
||||||
Widget _buildOperationStatusSection(OperationFormState state) {
|
Widget _cardAnagrafica(OperationFormState state) {
|
||||||
return Column(
|
return _buildCard(
|
||||||
|
title: 'Cliente e Riferimento',
|
||||||
|
icon: Icons.person,
|
||||||
|
themeColor: Colors.indigo,
|
||||||
|
children: [
|
||||||
|
SharedCustomerSection(
|
||||||
|
customer: state.operation.customer,
|
||||||
|
onCustomerSelected: (customer) => context
|
||||||
|
.read<OperationFormCubit>()
|
||||||
|
.updateFields(customer: customer),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
TextFormField(
|
||||||
|
controller: _referenceController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
labelText: 'Riferimento (es. Telefono, Targa...)',
|
||||||
|
prefixIcon: Icon(Icons.tag),
|
||||||
|
),
|
||||||
|
validator: (v) =>
|
||||||
|
v == null || v.isEmpty ? 'Inserisci un riferimento' : null,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _cardEsito(OperationFormState state) {
|
||||||
|
return _buildCard(
|
||||||
|
title: 'Esito Pratica',
|
||||||
|
icon: Icons.fact_check,
|
||||||
|
themeColor: _getStatusColor(state.operation.status),
|
||||||
children: [
|
children: [
|
||||||
_buildSectionTitle('Esito / Stato Operazione'),
|
|
||||||
Container(
|
Container(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
@@ -416,12 +429,8 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
Icons.arrow_drop_down,
|
Icons.arrow_drop_down,
|
||||||
color: _getStatusColor(state.operation.status),
|
color: _getStatusColor(state.operation.status),
|
||||||
),
|
),
|
||||||
items: OperationStatus.values
|
items: OperationStatus.values.map((status) {
|
||||||
/* .where(
|
return DropdownMenuItem(
|
||||||
(s) => s != OperationStatus.draft,
|
|
||||||
) // Nascondiamo 'Bozza' dal menu */
|
|
||||||
.map(
|
|
||||||
(status) => DropdownMenuItem(
|
|
||||||
value: status,
|
value: status,
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
@@ -442,16 +451,13 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
)
|
}).toList(),
|
||||||
.toList(),
|
|
||||||
onChanged: (newStatus) {
|
onChanged: (newStatus) {
|
||||||
if (newStatus != null) {
|
if (newStatus != null)
|
||||||
// Assicurati che il metodo updateFields nel tuo Cubit accetti anche 'status'
|
|
||||||
context.read<OperationFormCubit>().updateFields(
|
context.read<OperationFormCubit>().updateFields(
|
||||||
status: newStatus,
|
status: newStatus,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -459,37 +465,20 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
Text(
|
Text(
|
||||||
state.operation.status == OperationStatus.success
|
state.operation.status == OperationStatus.success
|
||||||
? 'Lascia OK se la pratica è stata caricata con successo.'
|
? 'Lascia OK se caricata con successo.'
|
||||||
: 'Attenzione: la pratica verrà salvata come ${state.operation.status.displayName}.',
|
: 'Attenzione: pratica salvata in stato anomalo.',
|
||||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
|
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildCustomerSection(OperationFormState state) {
|
Widget _cardDettagli(OperationFormState state) {
|
||||||
return SharedCustomerSection(
|
return _buildCard(
|
||||||
customer: state.operation.customer,
|
title: 'Dettagli Servizio',
|
||||||
onCustomerSelected: (customer) {
|
icon: Icons.design_services,
|
||||||
context.read<OperationFormCubit>().updateFields(customer: customer);
|
themeColor: Colors.deepOrange,
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildReferenceSection(OperationFormState state) {
|
|
||||||
return TextFormField(
|
|
||||||
controller: _referenceController,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
labelText: 'Riferimento (es. numero di telefono, targa...)',
|
|
||||||
prefixIcon: Icon(Icons.tag),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildOperationTypeSection(OperationFormState state) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
children: [
|
||||||
_buildSectionTitle('Cosa stiamo facendo?'),
|
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: 8.0,
|
spacing: 8.0,
|
||||||
runSpacing: 8.0,
|
runSpacing: 8.0,
|
||||||
@@ -498,104 +487,140 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
label: Text(type),
|
label: Text(type),
|
||||||
selected: state.operation.type == type,
|
selected: state.operation.type == type,
|
||||||
onSelected: (selected) {
|
onSelected: (selected) {
|
||||||
if (selected) {
|
if (selected)
|
||||||
context.read<OperationFormCubit>().setTypeWithSmartDefault(
|
context.read<OperationFormCubit>().setTypeWithSmartDefault(
|
||||||
type,
|
type,
|
||||||
);
|
);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}).toList(),
|
}).toList(),
|
||||||
),
|
),
|
||||||
],
|
const Divider(height: 32),
|
||||||
);
|
Row(
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildDetailsSection(OperationFormState state) {
|
|
||||||
return Column(
|
|
||||||
children: [
|
children: [
|
||||||
_buildSectionTitle('Dettagli Servizio'),
|
const Text(
|
||||||
OperationDetailsSection(
|
'Quantità:',
|
||||||
currentOp: state.operation,
|
style: TextStyle(fontWeight: FontWeight.bold),
|
||||||
currentType: state.operation.type,
|
|
||||||
freeTextSubtypeController: _freeTextSubtypeController,
|
|
||||||
freeTextDescriptionController: _freeTextDescriptionController,
|
|
||||||
durationQuickPicks: _buildDurationQuickPicks(state.operation),
|
|
||||||
),
|
),
|
||||||
],
|
const Spacer(),
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildQuantitySection(OperationFormState state) {
|
|
||||||
return Row(
|
|
||||||
children: [
|
|
||||||
const Text('Quantità: '),
|
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.remove),
|
icon: const Icon(Icons.remove_circle_outline),
|
||||||
onPressed: () {
|
onPressed: state.operation.quantity > 1
|
||||||
final q = state.operation.quantity;
|
? () => context.read<OperationFormCubit>().updateFields(
|
||||||
if (q > 1) {
|
quantity: state.operation.quantity - 1,
|
||||||
context.read<OperationFormCubit>().updateFields(quantity: q - 1);
|
)
|
||||||
}
|
: null,
|
||||||
},
|
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
'${state.operation.quantity}',
|
'${state.operation.quantity}',
|
||||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||||
),
|
),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.add),
|
icon: const Icon(Icons.add_circle_outline),
|
||||||
onPressed: () {
|
onPressed: () => context.read<OperationFormCubit>().updateFields(
|
||||||
final q = state.operation.quantity;
|
quantity: state.operation.quantity + 1,
|
||||||
context.read<OperationFormCubit>().updateFields(quantity: q + 1);
|
),
|
||||||
},
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(height: 32),
|
||||||
|
OperationDetailsSection(
|
||||||
|
currentOp: state.operation,
|
||||||
|
currentType: state.operation.type,
|
||||||
|
freeTextSubtypeController: _freeTextSubtypeController,
|
||||||
|
freeTextDescriptionController: _freeTextDescriptionController,
|
||||||
|
durationQuickPicks: _buildDurationQuickPicks(),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildAttachmentSection(OperationFormState state) {
|
Widget _cardNote(OperationFormState state) {
|
||||||
return SharedAttachmentsSection(
|
return _buildCard(
|
||||||
parentType: AttachmentParentType.operation,
|
title: 'Note Interne',
|
||||||
parentId: state.operation.id,
|
icon: Icons.notes,
|
||||||
titleForUpload: state.operation.customer?.name ?? 'Nuova pratica',
|
themeColor: Colors.teal,
|
||||||
onGenerateIdForQr: _generateIdForQr,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildMainFormContent(
|
|
||||||
ThemeData theme,
|
|
||||||
OperationFormState state,
|
|
||||||
OperationStatus displayStatus, {
|
|
||||||
bool showFiles = true,
|
|
||||||
}) {
|
|
||||||
return Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
/* _buildStaffSection(state),
|
TextFormField(
|
||||||
const Divider(height: 50), */
|
controller: _noteController,
|
||||||
_buildOperationStatusSection(state),
|
maxLines: 5,
|
||||||
const Divider(height: 32),
|
decoration: InputDecoration(
|
||||||
_buildCustomerSection(state),
|
hintText: 'Incolla seriali, ICCID, IBAN...',
|
||||||
const SizedBox(height: 16),
|
alignLabelWithHint: true,
|
||||||
_buildReferenceSection(state),
|
fillColor: Colors.teal.withValues(alpha: 0.05),
|
||||||
const Divider(height: 50),
|
filled: true,
|
||||||
_buildOperationTypeSection(state),
|
border: OutlineInputBorder(
|
||||||
const SizedBox(height: 16),
|
borderRadius: BorderRadius.circular(12),
|
||||||
_buildQuantitySection(state),
|
borderSide: BorderSide.none,
|
||||||
const Divider(height: 50),
|
),
|
||||||
_buildDetailsSection(state),
|
),
|
||||||
const Divider(height: 50),
|
),
|
||||||
|
|
||||||
// QUANTITÀ
|
|
||||||
const Divider(height: 32),
|
|
||||||
|
|
||||||
if (showFiles) ...[_buildAttachmentSection(state)],
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDurationQuickPicks(OperationModel? currentOp) {
|
Widget _cardAllegati(OperationFormState state) {
|
||||||
|
return _buildCard(
|
||||||
|
title: 'Allegati e Documenti',
|
||||||
|
icon: Icons.attach_file,
|
||||||
|
themeColor: Colors.deepPurple,
|
||||||
|
children: [
|
||||||
|
SharedFilesSection(
|
||||||
|
titleNameForUpload: state.operation.customer?.name ?? 'Nuova Pratica',
|
||||||
|
onGenerateIdForQr: _generateIdForQr,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- WIDGET BASE PER LA CARD ---
|
||||||
|
Widget _buildCard({
|
||||||
|
required String title,
|
||||||
|
required IconData icon,
|
||||||
|
required Color themeColor,
|
||||||
|
required List<Widget> children,
|
||||||
|
}) {
|
||||||
|
return Card(
|
||||||
|
margin: const EdgeInsets.only(bottom: 24),
|
||||||
|
elevation: 0,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
side: BorderSide(color: themeColor.withValues(alpha: 0.3), width: 1),
|
||||||
|
),
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(20.0),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
padding: const EdgeInsets.all(8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: themeColor.withValues(alpha: 0.1),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Icon(icon, color: themeColor),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(
|
||||||
|
title,
|
||||||
|
style: Theme.of(context).textTheme.titleLarge?.copyWith(
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: themeColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const Divider(height: 32),
|
||||||
|
...children,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildDurationQuickPicks() {
|
||||||
final durations = [3, 6, 12, 24, 30, 36, 48];
|
final durations = [3, 6, 12, 24, 30, 36, 48];
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -636,46 +661,4 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildNotesSection({required bool isDesktop}) {
|
|
||||||
final title = _buildSectionTitle('Note Interne');
|
|
||||||
final noteField = TextFormField(
|
|
||||||
controller: _noteController,
|
|
||||||
keyboardType: TextInputType.multiline,
|
|
||||||
minLines: isDesktop ? null : 5,
|
|
||||||
maxLines: null,
|
|
||||||
expands: isDesktop,
|
|
||||||
textAlignVertical: TextAlignVertical.top,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
hintText: 'Incolla qui seriali, ICCID, IBAN, indirizzi...',
|
|
||||||
alignLabelWithHint: true,
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return isDesktop
|
|
||||||
? Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
title,
|
|
||||||
const SizedBox(height: 8),
|
|
||||||
Expanded(child: noteField),
|
|
||||||
],
|
|
||||||
)
|
|
||||||
: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [title, const SizedBox(height: 8), noteField],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildSectionTitle(String title) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.only(bottom: 12.0),
|
|
||||||
child: Text(
|
|
||||||
title,
|
|
||||||
style: Theme.of(
|
|
||||||
context,
|
|
||||||
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class OperationDetailsSection extends StatelessWidget {
|
|||||||
switch (operationType) {
|
switch (operationType) {
|
||||||
case 'AL' || 'MNP':
|
case 'AL' || 'MNP':
|
||||||
return provider.roles.contains(ProviderRole.mobile);
|
return provider.roles.contains(ProviderRole.mobile);
|
||||||
case 'NIP':
|
case 'NIP' || 'FWA':
|
||||||
return provider.roles.contains(ProviderRole.landline);
|
return provider.roles.contains(ProviderRole.landline);
|
||||||
case 'UNICA':
|
case 'UNICA':
|
||||||
return provider.roles.contains(ProviderRole.landline) ||
|
return provider.roles.contains(ProviderRole.landline) ||
|
||||||
|
|||||||
@@ -160,7 +160,7 @@ class TicketShippingCubit extends Cubit<TicketShippingState> {
|
|||||||
await _repository.createShipmentWithPdf(
|
await _repository.createShipmentWithPdf(
|
||||||
document: state.document,
|
document: state.document,
|
||||||
pdfBytes: pdfBytes,
|
pdfBytes: pdfBytes,
|
||||||
newTicketStatus: newTicketStatus.value,
|
newTicketStatus: newTicketStatus,
|
||||||
);
|
);
|
||||||
await Printing.layoutPdf(
|
await Printing.layoutPdf(
|
||||||
onLayout: (format) async => pdfBytes,
|
onLayout: (format) async => pdfBytes,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import 'package:flux/features/attachments/data/attachments_repository.dart';
|
|||||||
import 'package:flux/features/tickets/models/shipping_document_model.dart';
|
import 'package:flux/features/tickets/models/shipping_document_model.dart';
|
||||||
import 'package:flux/features/master_data/providers/models/provider_model.dart';
|
import 'package:flux/features/master_data/providers/models/provider_model.dart';
|
||||||
import 'package:flux/features/master_data/providers/models/provider_role.dart';
|
import 'package:flux/features/master_data/providers/models/provider_role.dart';
|
||||||
|
import 'package:flux/features/tickets/models/ticket_model.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart' hide Bucket;
|
import 'package:supabase_flutter/supabase_flutter.dart' hide Bucket;
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ class TicketsShippingRepository {
|
|||||||
Future<void> createShipmentWithPdf({
|
Future<void> createShipmentWithPdf({
|
||||||
required ShippingDocumentModel document,
|
required ShippingDocumentModel document,
|
||||||
required Uint8List pdfBytes,
|
required Uint8List pdfBytes,
|
||||||
required String newTicketStatus,
|
required TicketStatus newTicketStatus,
|
||||||
}) async {
|
}) async {
|
||||||
try {
|
try {
|
||||||
final attachmentsRepo = GetIt.I.get<AttachmentsRepository>();
|
final attachmentsRepo = GetIt.I.get<AttachmentsRepository>();
|
||||||
@@ -124,8 +125,8 @@ class TicketsShippingRepository {
|
|||||||
await _supabase
|
await _supabase
|
||||||
.from('ticket')
|
.from('ticket')
|
||||||
.update({
|
.update({
|
||||||
'ticket_status': newTicketStatus,
|
'ticket_status': newTicketStatus.value,
|
||||||
'shipment_document_id': documentId,
|
'shipping_document_id': documentId,
|
||||||
})
|
})
|
||||||
.inFilter('id', document.ticketIds);
|
.inFilter('id', document.ticketIds);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ import 'package:flux/core/data/core_repository.dart';
|
|||||||
import 'package:flux/core/routes/app_router.dart';
|
import 'package:flux/core/routes/app_router.dart';
|
||||||
import 'package:flux/core/theme/theme.dart';
|
import 'package:flux/core/theme/theme.dart';
|
||||||
import 'package:flux/core/theme/bloc/theme_bloc.dart';
|
import 'package:flux/core/theme/bloc/theme_bloc.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/data/customer_repository.dart';
|
import 'package:flux/features/customers/data/customer_repository.dart';
|
||||||
import 'package:flux/features/master_data/products/blocs/product_cubit.dart';
|
import 'package:flux/features/master_data/products/blocs/product_cubit.dart';
|
||||||
import 'package:flux/features/master_data/products/data/product_repository.dart';
|
import 'package:flux/features/master_data/products/data/product_repository.dart';
|
||||||
@@ -59,7 +59,7 @@ void main() async {
|
|||||||
|
|
||||||
// Cubit delle feature
|
// Cubit delle feature
|
||||||
BlocProvider<StoreCubit>(create: (_) => StoreCubit()),
|
BlocProvider<StoreCubit>(create: (_) => StoreCubit()),
|
||||||
BlocProvider<CustomersCubit>(create: (_) => CustomersCubit()),
|
BlocProvider<CustomersListCubit>(create: (_) => CustomersListCubit()),
|
||||||
BlocProvider<ProductsCubit>(create: (_) => ProductsCubit()),
|
BlocProvider<ProductsCubit>(create: (_) => ProductsCubit()),
|
||||||
BlocProvider<StaffCubit>(
|
BlocProvider<StaffCubit>(
|
||||||
create: (_) => StaffCubit()
|
create: (_) => StaffCubit()
|
||||||
|
|||||||
Reference in New Issue
Block a user