This commit is contained in:
2026-05-01 10:11:44 +02:00
parent 9c8576ada5
commit f8bcac51e1
48 changed files with 1187 additions and 1141 deletions

View File

@@ -1,12 +1,12 @@
import 'package:flutter/material.dart';
class ServiceActionCard extends StatelessWidget {
class OperationActionCard extends StatelessWidget {
final String title;
final IconData icon;
final VoidCallback onTap;
final Color color;
final int count;
const ServiceActionCard({
const OperationActionCard({
super.key,
required this.title,
required this.icon,

View File

@@ -5,9 +5,9 @@ import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/core/widgets/image_viewer_widget.dart';
import 'package:flux/core/widgets/pdf_viewer_widget.dart';
import 'package:flux/core/widgets/qr_upload_dialog.dart';
import 'package:flux/features/operations/blocs/service_files_bloc.dart';
import 'package:flux/features/operations/blocs/operation_files_bloc.dart';
import 'package:flux/features/operations/blocs/operations_cubit.dart';
import 'package:flux/features/operations/models/service_file_model.dart';
import 'package:flux/features/operations/models/operation_file_model.dart';
class AttachmentsSection extends StatelessWidget {
const AttachmentsSection({super.key});
@@ -22,27 +22,29 @@ class AttachmentsSection extends StatelessWidget {
);
if (result != null && context.mounted) {
context.read<ServiceFilesBloc>().add(AddServiceFilesEvent(result.files));
context.read<OperationFilesBloc>().add(
AddOperationFilesEvent(result.files),
);
}
}
@override
Widget build(BuildContext context) {
ServiceFilesBloc serviceFilesBloc = BlocProvider.of<ServiceFilesBloc>(
OperationFilesBloc operationFilesBloc = BlocProvider.of<OperationFilesBloc>(
context,
);
return BlocListener<ServicesCubit, ServicesState>(
return BlocListener<OperationsCubit, OperationsState>(
listenWhen: (previous, current) =>
previous.currentService?.id == null &&
current.currentService?.id != null,
previous.currentOperation?.id == null &&
current.currentOperation?.id != null,
listener: (context, state) {
// FIGASSA! La pratica è stata salvata e ora ha un ID.
// Diciamo al Bloc dei file di agganciarsi al database.
final newId = state.currentService!.id!;
context.read<ServiceFilesBloc>().add(ServiceSavedEvent(newId));
final newId = state.currentOperation!.id!;
context.read<OperationFilesBloc>().add(OperationsavedEvent(newId));
},
child: BlocBuilder<ServiceFilesBloc, ServiceFilesState>(
child: BlocBuilder<OperationFilesBloc, OperationFilesState>(
builder: (context, state) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -125,8 +127,8 @@ class AttachmentsSection extends StatelessWidget {
final isSelected = state.selectedFiles.contains(file);
return GestureDetector(
onTap: () => serviceFilesBloc.add(
ToggleServiceFileSelectionEvent(file),
onTap: () => operationFilesBloc.add(
ToggleOperationFileSelectionEvent(file),
),
onDoubleTap: () => _handleDoubleClick(context, file),
child: Card(
@@ -216,7 +218,7 @@ class AttachmentsSection extends StatelessWidget {
label: const Text("Elimina"),
onPressed: () {
// Qui lancerai l'evento per eliminare i file selezionati!
// Es: serviceFilesBloc.add(DeleteSelectedFilesEvent());
// Es: operationFilesBloc.add(DeleteSelectedFilesEvent());
},
),
const SizedBox(width: 8),
@@ -243,14 +245,14 @@ class AttachmentsSection extends StatelessWidget {
}
Future<void> _handleGenerateQr(BuildContext context) async {
final cubit = context.read<ServicesCubit>();
var currentService = cubit.state.currentService;
final cubit = context.read<OperationsCubit>();
var currentOperation = cubit.state.currentOperation;
// 1. CATTURIAMO IL BLOC MENTRE SIAMO ANCORA NELLA PAGINA
final serviceFilesBloc = context.read<ServiceFilesBloc>();
final operationFilesBloc = context.read<OperationFilesBloc>();
// 2. SE LA PRATICA E' NUOVA (Manca l'ID)
if (currentService == null || currentService.id == null) {
if (currentOperation == null || currentOperation.id == null) {
// NIENTE BlocListener qui! Solo un semplice Dialog di conferma
final bool? confirm = await showDialog<bool>(
context: context,
@@ -275,42 +277,42 @@ class AttachmentsSection extends StatelessWidget {
if (confirm != true) return; // Utente ha annullato
// Salviamo forzatamente in bozza
await cubit.saveCurrentService(
await cubit.saveCurrentOperation(
isBozza: true,
shouldPop: false,
files: serviceFilesBloc.state.localFiles,
files: operationFilesBloc.state.localFiles,
);
// Recuperiamo il servizio aggiornato con l'ID!
currentService = cubit.state.currentService;
currentOperation = cubit.state.currentOperation;
if (currentService?.id == null) return;
if (currentOperation?.id == null) return;
}
// 3. MOSTRIAMO IL QR CODE (Con il Ponte e l'Auto-Chiusura!)
if (context.mounted) {
final nomePratica = "Pratica ${currentService?.customerDisplayName ?? ''}"
.trim();
final nomePratica =
"Pratica ${currentOperation?.customerDisplayName ?? ''}".trim();
showDialog(
context: context,
builder: (dialogContext) => BlocProvider.value(
// INIETTIAMO IL BLOC NEL CONTESTO DEL DIALOG ALIENO
value: serviceFilesBloc,
value: operationFilesBloc,
// ORA METTIAMO L'AUTO-CHIUSURA SUL QR CODE!
child: BlocListener<ServiceFilesBloc, ServiceFilesState>(
child: BlocListener<OperationFilesBloc, OperationFilesState>(
listener: (context, state) {
// Se arrivano file remoti e lo stato è success, chiudiamo il QR!
// (Nota: usiamo dialogContext per assicurarci di chiudere il popup giusto)
if (state.status == ServiceFilesStatus.success &&
if (state.status == OperationFilesStatus.success &&
state.remoteFiles.isNotEmpty) {
Navigator.of(dialogContext).pop();
}
},
child: QrUploadDialog(
deepLinkUrl:
'fluxapp:///operation/${currentService!.id}/upload?name=${Uri.encodeComponent(nomePratica)}',
'fluxapp:///operation/${currentOperation!.id}/upload?name=${Uri.encodeComponent(nomePratica)}',
title: 'Scatta per\n$nomePratica',
),
),
@@ -322,7 +324,7 @@ class AttachmentsSection extends StatelessWidget {
// --- LOGICA DI COPIA AL CLIENTE ---
void saveAndCopyFilesToCustomer(
BuildContext context,
List<ServiceFileModel> files,
List<OperationFileModel> files,
) {
showDialog(
context: context,
@@ -341,7 +343,7 @@ class AttachmentsSection extends StatelessWidget {
onPressed: () {
Navigator.pop(ctx);
// 1. Diciamo al Cubit di salvare in Bozza e fare la copia
context.read<ServicesCubit>().saveAndCopyFileToCustomer(files);
context.read<OperationsCubit>().saveAndCopyFileToCustomer(files);
},
child: const Text("Salva e Copia"),
),
@@ -351,7 +353,7 @@ class AttachmentsSection extends StatelessWidget {
}
// --- LOGICA DI VISUALIZZAZIONE OVERLAY ---
void _handleDoubleClick(BuildContext context, ServiceFileModel file) {
void _handleDoubleClick(BuildContext context, OperationFileModel file) {
showDialog(
context: context,
barrierDismissible: true,

View File

@@ -1,9 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flux/features/customers/ui/customer_search_sheet.dart';
import 'package:flux/features/operations/models/service_model.dart';
import 'package:flux/features/operations/models/operation_model.dart';
class CustomerSection extends StatelessWidget {
final ServiceModel operation;
final OperationModel operation;
const CustomerSection({super.key, required this.operation});

View File

@@ -2,32 +2,32 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/master_data/providers/blocs/provider_cubit.dart';
import 'package:flux/features/master_data/providers/models/provider_model.dart';
import 'package:flux/features/operations/models/energy_service_model.dart'; // Assicurati degli import
import 'package:flux/features/operations/models/energy_operation_model.dart'; // Assicurati degli import
class EnergyServiceDialog extends StatefulWidget {
final List<EnergyServiceModel> initialServices;
class EnergyOperationDialog extends StatefulWidget {
final List<EnergyOperationModel> initialOperations;
final String
currentStoreId; // Ci serve per sapere per quale negozio caricare i gestori
const EnergyServiceDialog({
const EnergyOperationDialog({
super.key,
required this.initialServices,
required this.initialOperations,
required this.currentStoreId,
});
@override
State<EnergyServiceDialog> createState() => _EnergyServiceDialogState();
State<EnergyOperationDialog> createState() => _EnergyOperationDialogState();
}
class _EnergyServiceDialogState extends State<EnergyServiceDialog> {
class _EnergyOperationDialogState extends State<EnergyOperationDialog> {
// Lista temporanea per non "sporcare" il cubit finché non si preme Conferma
late List<EnergyServiceModel> _tempList;
late List<EnergyOperationModel> _tempList;
bool _isAddingNew = false;
@override
void initState() {
super.initState();
_tempList = List.from(widget.initialServices);
_tempList = List.from(widget.initialOperations);
// Al caricamento della modale, chiediamo al Cubit di recuperare i gestori veri!
context.read<ProvidersCubit>().loadActiveProvidersForStore(
widget.currentStoreId,
@@ -52,9 +52,9 @@ class _EnergyServiceDialogState extends State<EnergyServiceDialog> {
// Cambia vista in base al flag
child: _isAddingNew
? _EnergyForm(
onSave: (newService) {
onSave: (newOperation) {
setState(() {
_tempList.add(newService);
_tempList.add(newOperation);
_isAddingNew = false; // Torna alla lista
});
},
@@ -101,7 +101,7 @@ class _EnergyServiceDialogState extends State<EnergyServiceDialog> {
// VISTA 1: LA LISTA DEI CONTRATTI
// ==========================================
class _EnergyList extends StatelessWidget {
final List<EnergyServiceModel> operations;
final List<EnergyOperationModel> operations;
final List<ProviderModel>
activeProviders; // <--- NUOVO: La lista vera dal Cubit
final Function(int) onDelete;
@@ -193,7 +193,7 @@ class _EnergyList extends StatelessWidget {
// VISTA 2: IL FORM DI INSERIMENTO
// ==========================================
class _EnergyForm extends StatefulWidget {
final Function(EnergyServiceModel) onSave;
final Function(EnergyOperationModel) onSave;
final VoidCallback onCancel;
const _EnergyForm({required this.onSave, required this.onCancel});
@@ -400,12 +400,12 @@ class _EnergyFormState extends State<_EnergyForm> {
(_selectedProviderId == null || _selectedExpiration == null)
? null // Disabilitato se mancano dati obbligatori
: () {
final newService = EnergyServiceModel(
final newOperation = EnergyOperationModel(
type: _selectedType,
expiration: _selectedExpiration!,
providerId: _selectedProviderId!,
);
widget.onSave(newService);
widget.onSave(newOperation);
},
child: const Text("Salva Contratto"),
),

View File

@@ -3,34 +3,34 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/core/blocs/session/session_cubit.dart';
import 'package:flux/features/master_data/providers/blocs/provider_cubit.dart';
import 'package:flux/features/master_data/providers/models/provider_model.dart';
import 'package:flux/features/operations/data/services_repository.dart';
import 'package:flux/features/operations/models/entertainment_service_model.dart';
import 'package:flux/features/operations/data/operations_repository.dart';
import 'package:flux/features/operations/models/entertainment_operation_model.dart';
import 'package:get_it/get_it.dart';
class EntertainmentServiceDialog extends StatefulWidget {
final List<EntertainmentServiceModel> initialServices;
class EntertainmentOperationDialog extends StatefulWidget {
final List<EntertainmentOperationModel> initialOperations;
final String currentStoreId;
const EntertainmentServiceDialog({
const EntertainmentOperationDialog({
super.key,
required this.initialServices,
required this.initialOperations,
required this.currentStoreId,
});
@override
State<EntertainmentServiceDialog> createState() =>
_EntertainmentServiceDialogState();
State<EntertainmentOperationDialog> createState() =>
_EntertainmentOperationDialogState();
}
class _EntertainmentServiceDialogState
extends State<EntertainmentServiceDialog> {
late List<EntertainmentServiceModel> _tempList;
class _EntertainmentOperationDialogState
extends State<EntertainmentOperationDialog> {
late List<EntertainmentOperationModel> _tempList;
bool _isAddingNew = false;
@override
void initState() {
super.initState();
_tempList = List.from(widget.initialServices);
_tempList = List.from(widget.initialOperations);
// Carichiamo i provider attivi per lo store corrente
context.read<ProvidersCubit>().loadActiveProvidersForStore(
widget.currentStoreId,
@@ -57,8 +57,8 @@ class _EntertainmentServiceDialogState
child: _isAddingNew
? _EntertainmentForm(
// Il form che abbiamo creato prima
onSave: (newService) => setState(() {
_tempList.add(newService);
onSave: (newOperation) => setState(() {
_tempList.add(newOperation);
_isAddingNew = false;
}),
onCancel: () => setState(() => _isAddingNew = false),
@@ -94,7 +94,7 @@ class _EntertainmentServiceDialogState
}
class _EntertainmentList extends StatelessWidget {
final List<EntertainmentServiceModel> operations;
final List<EntertainmentOperationModel> operations;
final List<ProviderModel> allProviders;
final Function(int) onDelete;
final VoidCallback onAddTap;
@@ -194,7 +194,7 @@ class _EntertainmentList extends StatelessWidget {
// ---ENTERTAINMENT FORM (MODALE)---
class _EntertainmentForm extends StatefulWidget {
final Function(EntertainmentServiceModel) onSave;
final Function(EntertainmentOperationModel) onSave;
final VoidCallback onCancel;
const _EntertainmentForm({required this.onSave, required this.onCancel});
@@ -280,7 +280,7 @@ class _EntertainmentFormState extends State<_EntertainmentForm> {
const SizedBox(height: 8),
// Suggerimenti rapidi (Chip)
FutureBuilder<List<String>>(
future: GetIt.I<ServicesRepository>().fetchTopEntertainmentTypes(
future: GetIt.I<OperationsRepository>().fetchTopEntertainmentTypes(
GetIt.I<SessionCubit>().state.company!.id!,
),
builder: (context, snapshot) {
@@ -376,7 +376,7 @@ class _EntertainmentFormState extends State<_EntertainmentForm> {
(_selectedProviderId == null || _typeController.text.isEmpty)
? null
: () => widget.onSave(
EntertainmentServiceModel(
EntertainmentOperationModel(
providerId: _selectedProviderId!,
type: _typeController.text,
constrained: _isConstrained,

View File

@@ -5,36 +5,36 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/master_data/products/blocs/product_cubit.dart';
import 'package:flux/features/master_data/products/models/model_model.dart';
import 'package:flux/features/master_data/providers/blocs/provider_cubit.dart';
import 'package:flux/features/operations/models/fin_service_model.dart';
import 'package:flux/features/operations/models/fin_operation_model.dart';
import 'package:flux/features/master_data/providers/models/provider_model.dart';
// ===========================================================================
// DIALOG PRINCIPALE
// ===========================================================================
class FinanceServiceDialog extends StatefulWidget {
final List<FinServiceModel> initialServices;
class FinanceOperationDialog extends StatefulWidget {
final List<FinOperationModel> initialOperations;
final String currentStoreId;
final ProductCubit productCubit;
const FinanceServiceDialog({
const FinanceOperationDialog({
super.key,
required this.initialServices,
required this.initialOperations,
required this.currentStoreId,
required this.productCubit,
});
@override
State<FinanceServiceDialog> createState() => _FinanceServiceDialogState();
State<FinanceOperationDialog> createState() => _FinanceOperationDialogState();
}
class _FinanceServiceDialogState extends State<FinanceServiceDialog> {
late List<FinServiceModel> _tempList;
class _FinanceOperationDialogState extends State<FinanceOperationDialog> {
late List<FinOperationModel> _tempList;
bool _isAddingNew = false;
@override
void initState() {
super.initState();
_tempList = List.from(widget.initialServices);
_tempList = List.from(widget.initialOperations);
// Carichiamo i dati necessari dai Cubit
context.read<ProvidersCubit>().loadActiveProvidersForStore(
widget.currentStoreId,
@@ -109,7 +109,7 @@ class _FinanceServiceDialogState extends State<FinanceServiceDialog> {
// VISTA LISTA (STORICA)
// ===========================================================================
class _FinanceList extends StatelessWidget {
final List<FinServiceModel> operations;
final List<FinOperationModel> operations;
final List<ProviderModel> allProviders;
final List<ModelModel> allModels;
final Function(int) onDelete;
@@ -221,7 +221,7 @@ class _FinanceList extends StatelessWidget {
// FORM CON OMNI-SEARCH
// ===========================================================================
class _FinanceForm extends StatefulWidget {
final Function(FinServiceModel) onSave;
final Function(FinOperationModel) onSave;
final VoidCallback onCancel;
const _FinanceForm({required this.onSave, required this.onCancel});
@@ -428,7 +428,7 @@ class _FinanceFormState extends State<_FinanceForm> {
: () {
final now = DateTime.now();
widget.onSave(
FinServiceModel(
FinOperationModel(
providerId: _selectedProviderId!,
modelId: _selectedModel!.id!,
expiration: DateTime(

View File

@@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/operations/blocs/operations_cubit.dart';
import 'package:flux/features/operations/models/service_model.dart';
import 'package:flux/features/operations/models/operation_model.dart';
class GeneralInfoSection extends StatelessWidget {
final ServiceModel operation;
final OperationModel operation;
const GeneralInfoSection({super.key, required this.operation});
@override
@@ -44,7 +44,7 @@ class GeneralInfoSection extends StatelessWidget {
prefixIcon: Icon(Icons.phone),
),
onChanged: (val) {
context.read<ServicesCubit>().updateField(number: val);
context.read<OperationsCubit>().updateField(number: val);
},
),
const SizedBox(height: 16),
@@ -63,7 +63,7 @@ class GeneralInfoSection extends StatelessWidget {
activeThumbColor: Colors.orange,
contentPadding: EdgeInsets.zero,
onChanged: (val) {
context.read<ServicesCubit>().updateField(isBozza: val);
context.read<OperationsCubit>().updateField(isBozza: val);
},
),
),
@@ -79,7 +79,9 @@ class GeneralInfoSection extends StatelessWidget {
activeThumbColor: Colors.green,
contentPadding: EdgeInsets.zero,
onChanged: (val) {
context.read<ServicesCubit>().updateField(resultOk: val);
context.read<OperationsCubit>().updateField(
resultOk: val,
);
},
),
),
@@ -100,7 +102,7 @@ class GeneralInfoSection extends StatelessWidget {
alignLabelWithHint: true,
),
onChanged: (val) {
context.read<ServicesCubit>().updateField(note: val);
context.read<OperationsCubit>().updateField(note: val);
},
),
],

View File

@@ -1,49 +1,49 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/operations/blocs/operations_cubit.dart';
import 'package:flux/features/operations/models/service_model.dart';
import 'package:flux/features/operations/ui/service_form_screen/attachment_section.dart';
import 'package:flux/features/operations/ui/service_form_screen/customer_section.dart';
import 'package:flux/features/operations/ui/service_form_screen/general_info_section.dart';
import 'package:flux/features/operations/ui/service_form_screen/services_grid.dart';
import 'package:flux/features/operations/models/operation_model.dart';
import 'package:flux/features/operations/ui/operation_form_screen/attachment_section.dart';
import 'package:flux/features/operations/ui/operation_form_screen/customer_section.dart';
import 'package:flux/features/operations/ui/operation_form_screen/general_info_section.dart';
import 'package:flux/features/operations/ui/operation_form_screen/operations_grid.dart';
class ServiceFormScreen extends StatefulWidget {
final String? serviceId;
final ServiceModel? existingService; // <-- AGGIUNTO
class OperationFormScreen extends StatefulWidget {
final String? operationId;
final OperationModel? existingOperation; // <-- AGGIUNTO
const ServiceFormScreen({
const OperationFormScreen({
super.key,
this.serviceId,
this.existingService, // <-- AGGIUNTO
this.operationId,
this.existingOperation, // <-- AGGIUNTO
});
@override
State<ServiceFormScreen> createState() => _ServiceFormScreenState();
State<OperationFormScreen> createState() => _OperationFormScreenState();
}
class _ServiceFormScreenState extends State<ServiceFormScreen> {
class _OperationFormScreenState extends State<OperationFormScreen> {
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
// Diamo in pasto al Cubit tutto quello che abbiamo!
context.read<ServicesCubit>().initServiceForm(
existingService: widget.existingService,
serviceId: widget.serviceId,
context.read<OperationsCubit>().initOperationForm(
existingOperation: widget.existingOperation,
operationId: widget.operationId,
);
});
}
void _performSave(BuildContext context, {required bool isBozza}) {
FocusScope.of(context).unfocus();
context.read<ServicesCubit>().saveCurrentService(isBozza: isBozza);
context.read<OperationsCubit>().saveCurrentOperation(isBozza: isBozza);
}
@override
Widget build(BuildContext context) {
return BlocConsumer<ServicesCubit, ServicesState>(
return BlocConsumer<OperationsCubit, OperationsState>(
listener: (context, state) {
if (state.status == ServicesStatus.saved) {
if (state.status == OperationsStatus.saved) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Pratica salvata con successo!"),
@@ -52,7 +52,7 @@ class _ServiceFormScreenState extends State<ServiceFormScreen> {
);
Navigator.pop(context);
}
if (state.status == ServicesStatus.failure) {
if (state.status == OperationsStatus.failure) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Errore: ${state.errorMessage ?? ''}"),
@@ -60,7 +60,7 @@ class _ServiceFormScreenState extends State<ServiceFormScreen> {
),
);
}
if (state.status == ServicesStatus.savedNoPop) {
if (state.status == OperationsStatus.savedNoPop) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Pratica salvata con successo!"),
@@ -70,9 +70,9 @@ class _ServiceFormScreenState extends State<ServiceFormScreen> {
}
},
builder: (context, state) {
final operation = state.currentService;
final isSaving = state.status == ServicesStatus.saving;
final isEditMode = widget.serviceId != null;
final operation = state.currentOperation;
final isSaving = state.status == OperationsStatus.saving;
final isEditMode = widget.operationId != null;
return Scaffold(
appBar: AppBar(
@@ -120,7 +120,7 @@ class _ServiceFormScreenState extends State<ServiceFormScreen> {
GeneralInfoSection(operation: operation),
const SizedBox(height: 24),
ServicesGrid(operation: operation),
OperationsGrid(operation: operation),
const SizedBox(height: 32),
AttachmentsSection(),

View File

@@ -3,24 +3,25 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:image_picker/image_picker.dart';
import 'package:file_picker/file_picker.dart';
import 'package:flux/features/operations/blocs/service_files_bloc.dart';
import 'package:flux/features/operations/blocs/operation_files_bloc.dart';
class ServiceMobileUploadScreen extends StatefulWidget {
final String serviceId;
final String serviceName;
class OperationMobileUploadScreen extends StatefulWidget {
final String operationId;
final String operationName;
const ServiceMobileUploadScreen({
const OperationMobileUploadScreen({
super.key,
required this.serviceId,
required this.serviceName,
required this.operationId,
required this.operationName,
});
@override
State<ServiceMobileUploadScreen> createState() =>
_ServiceMobileUploadScreenState();
State<OperationMobileUploadScreen> createState() =>
_OperationMobileUploadScreenState();
}
class _ServiceMobileUploadScreenState extends State<ServiceMobileUploadScreen> {
class _OperationMobileUploadScreenState
extends State<OperationMobileUploadScreen> {
// 1. LA NOSTRA STAGING AREA (Il "Carrello")
final List<PlatformFile> _stagedFiles = [];
@@ -35,10 +36,10 @@ class _ServiceMobileUploadScreenState extends State<ServiceMobileUploadScreen> {
@override
Widget build(BuildContext context) {
return BlocListener<ServiceFilesBloc, ServiceFilesState>(
return BlocListener<OperationFilesBloc, OperationFilesState>(
listener: (context, state) {
// Quando il BLoC ci dice che ha finito l'upload (Success), chiudiamo la pagina!
if (state.status == ServiceFilesStatus.success && _isUploading) {
if (state.status == OperationFilesStatus.success && _isUploading) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text("Tutti i file caricati con successo! ✅"),
@@ -46,7 +47,7 @@ class _ServiceMobileUploadScreenState extends State<ServiceMobileUploadScreen> {
);
Navigator.of(context).pop();
}
if (state.status == ServiceFilesStatus.failure) {
if (state.status == OperationFilesStatus.failure) {
setState(() => _isUploading = false);
ScaffoldMessenger.of(
context,
@@ -55,7 +56,7 @@ class _ServiceMobileUploadScreenState extends State<ServiceMobileUploadScreen> {
},
child: Scaffold(
appBar: AppBar(
title: Text("Upload Pratica:\n${widget.serviceName}"),
title: Text("Upload Pratica:\n${widget.operationName}"),
automaticallyImplyLeading: !_isUploading,
),
body: Stack(
@@ -294,8 +295,8 @@ class _ServiceMobileUploadScreenState extends State<ServiceMobileUploadScreen> {
// Diciamo al BLoC di caricare tutti i file.
// Usiamo il tuo evento esistente per ogni file (il BLoC li metterà in coda)
final bloc = context.read<ServiceFilesBloc>();
bloc.add(UploadMultipleServiceFilesEvent(_stagedFiles));
final bloc = context.read<OperationFilesBloc>();
bloc.add(UploadMultipleOperationFilesEvent(_stagedFiles));
// N.B: Il Navigator.pop() viene chiamato dal BlocListener in alto quando lo stato diventa "success"!
}

View File

@@ -2,20 +2,20 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/master_data/products/blocs/product_cubit.dart';
import 'package:flux/features/operations/blocs/operations_cubit.dart';
import 'package:flux/features/operations/models/energy_service_model.dart';
import 'package:flux/features/operations/models/entertainment_service_model.dart';
import 'package:flux/features/operations/models/fin_service_model.dart';
import 'package:flux/features/operations/models/service_model.dart';
import 'package:flux/features/operations/ui/service_form_screen/action_card.dart';
import 'package:flux/features/operations/ui/service_form_screen/energy_service_dialog.dart';
import 'package:flux/features/operations/ui/service_form_screen/entertainment_service_card.dart';
import 'package:flux/features/operations/ui/service_form_screen/finance_service_dialog.dart';
import 'package:flux/features/operations/ui/service_form_screen/int_dialogs.dart'; // Assicurati di importare il modello
import 'package:flux/features/operations/models/energy_operation_model.dart';
import 'package:flux/features/operations/models/entertainment_operation_model.dart';
import 'package:flux/features/operations/models/fin_operation_model.dart';
import 'package:flux/features/operations/models/operation_model.dart';
import 'package:flux/features/operations/ui/operation_form_screen/action_card.dart';
import 'package:flux/features/operations/ui/operation_form_screen/energy_operation_dialog.dart';
import 'package:flux/features/operations/ui/operation_form_screen/entertainment_operation_card.dart';
import 'package:flux/features/operations/ui/operation_form_screen/finance_operation_dialog.dart';
import 'package:flux/features/operations/ui/operation_form_screen/int_dialogs.dart'; // Assicurati di importare il modello
class ServicesGrid extends StatelessWidget {
final ServiceModel operation;
class OperationsGrid extends StatelessWidget {
final OperationModel operation;
const ServicesGrid({super.key, required this.operation});
const OperationsGrid({super.key, required this.operation});
@override
Widget build(BuildContext context) {
@@ -60,7 +60,7 @@ class ServicesGrid extends StatelessWidget {
"AL",
operation.al,
(val) =>
context.read<ServicesCubit>().updateField(al: val),
context.read<OperationsCubit>().updateField(al: val),
),
),
ActionCard(
@@ -73,7 +73,7 @@ class ServicesGrid extends StatelessWidget {
"MNP",
operation.mnp,
(val) =>
context.read<ServicesCubit>().updateField(mnp: val),
context.read<OperationsCubit>().updateField(mnp: val),
),
),
ActionCard(
@@ -86,7 +86,7 @@ class ServicesGrid extends StatelessWidget {
"NIP",
operation.nip,
(val) =>
context.read<ServicesCubit>().updateField(nip: val),
context.read<OperationsCubit>().updateField(nip: val),
),
),
ActionCard(
@@ -98,8 +98,9 @@ class ServicesGrid extends StatelessWidget {
context,
"Unica",
operation.unica,
(val) =>
context.read<ServicesCubit>().updateField(unica: val),
(val) => context.read<OperationsCubit>().updateField(
unica: val,
),
),
),
ActionCard(
@@ -111,7 +112,7 @@ class ServicesGrid extends StatelessWidget {
context,
"Telepass",
operation.telepass,
(val) => context.read<ServicesCubit>().updateField(
(val) => context.read<OperationsCubit>().updateField(
telepass: val,
),
),
@@ -120,23 +121,24 @@ class ServicesGrid extends StatelessWidget {
// --- MODULI COMPLESSI (Le liste) ---
ActionCard(
label: "Energia",
count: operation.energyServices.length,
count: operation.energyOperations.length,
icon: Icons.bolt,
color: Colors.green,
onTap: () async {
// Apriamo la modale e aspettiamo il risultato
final result = await showDialog<List<EnergyServiceModel>>(
context: context,
builder: (context) => EnergyServiceDialog(
currentStoreId: operation.storeId,
initialServices: operation
.energyServices, // Passiamo la lista attuale
),
);
final result =
await showDialog<List<EnergyOperationModel>>(
context: context,
builder: (context) => EnergyOperationDialog(
currentStoreId: operation.storeId,
initialOperations: operation
.energyOperations, // Passiamo la lista attuale
),
);
// Se l'utente ha premuto "Conferma" e non "Annulla" o tap fuori
if (result != null && context.mounted) {
context.read<ServicesCubit>().updateEnergyServices(
context.read<OperationsCubit>().updateEnergyOperations(
result,
);
}
@@ -144,44 +146,47 @@ class ServicesGrid extends StatelessWidget {
),
ActionCard(
label: "Finanziam.",
count: operation.finServices.length,
count: operation.finOperations.length,
icon: Icons.euro_symbol,
color: Colors.teal,
onTap: () async {
final result = await showDialog<List<FinServiceModel>>(
final result = await showDialog<List<FinOperationModel>>(
context: context,
builder: (context) => FinanceServiceDialog(
builder: (context) => FinanceOperationDialog(
productCubit: context.read<ProductCubit>(),
currentStoreId: operation.storeId,
initialServices: operation
.finServices, // Passiamo la lista attuale
initialOperations: operation
.finOperations, // Passiamo la lista attuale
),
);
if (result != null && context.mounted) {
context.read<ServicesCubit>().updateFinServices(result);
context.read<OperationsCubit>().updateFinOperations(
result,
);
}
},
),
ActionCard(
label: "Intratten.",
count: operation.entertainmentServices.length,
count: operation.entertainmentOperations.length,
icon: Icons.movie_filter_outlined,
color: Colors.purple,
onTap: () async {
final result =
await showDialog<List<EntertainmentServiceModel>>(
await showDialog<List<EntertainmentOperationModel>>(
context: context,
builder: (context) => EntertainmentServiceDialog(
initialServices: operation.entertainmentServices,
builder: (context) => EntertainmentOperationDialog(
initialOperations:
operation.entertainmentOperations,
currentStoreId: operation.storeId,
),
);
if (result != null && context.mounted) {
context
.read<ServicesCubit>()
.updateEntertainmentServices(result);
.read<OperationsCubit>()
.updateEntertainmentOperations(result);
}
},
),

View File

@@ -1,19 +1,19 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flux/features/operations/blocs/operations_cubit.dart';
import 'package:flux/features/operations/models/service_model.dart';
import 'package:flux/features/operations/utils/service_actions.dart';
import 'package:flux/features/operations/models/operation_model.dart';
import 'package:flux/features/operations/utils/operation_actions.dart';
import 'package:go_router/go_router.dart';
// Importa i tuoi modelli e cubit
class ServicesScreen extends StatefulWidget {
const ServicesScreen({super.key});
class OperationsScreen extends StatefulWidget {
const OperationsScreen({super.key});
@override
State<ServicesScreen> createState() => _ServicesScreenState();
State<OperationsScreen> createState() => _OperationsScreenState();
}
class _ServicesScreenState extends State<ServicesScreen> {
class _OperationsScreenState extends State<OperationsScreen> {
final ScrollController _scrollController = ScrollController();
@override
@@ -22,12 +22,12 @@ class _ServicesScreenState extends State<ServicesScreen> {
// Agganciamo il listener per la paginazione (Scroll Infinito)
_scrollController.addListener(_onScroll);
// Carichiamo i servizi iniziali
context.read<ServicesCubit>().loadServices();
context.read<OperationsCubit>().loadOperations();
}
void _onScroll() {
if (_isBottom) {
context.read<ServicesCubit>().loadServices();
context.read<OperationsCubit>().loadOperations();
}
}
@@ -60,16 +60,16 @@ class _ServicesScreenState extends State<ServicesScreen> {
),
],
),
body: BlocBuilder<ServicesCubit, ServicesState>(
body: BlocBuilder<OperationsCubit, OperationsState>(
builder: (context, state) {
// 1. Stato di caricamento iniziale
if (state.status == ServicesStatus.loading &&
state.allServices.isEmpty) {
if (state.status == OperationsStatus.loading &&
state.allOperations.isEmpty) {
return const Center(child: CircularProgressIndicator());
}
// 2. Lista vuota
if (state.allServices.isEmpty) {
if (state.allOperations.isEmpty) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
@@ -77,9 +77,9 @@ class _ServicesScreenState extends State<ServicesScreen> {
const Text("Nessuna pratica trovata."),
const SizedBox(height: 10),
ElevatedButton(
onPressed: () => context.read<ServicesCubit>().loadServices(
refresh: true,
),
onPressed: () => context
.read<OperationsCubit>()
.loadOperations(refresh: true),
child: const Text("Riprova"),
),
],
@@ -90,15 +90,15 @@ class _ServicesScreenState extends State<ServicesScreen> {
// 3. La Lista (con Pull-to-refresh)
return RefreshIndicator(
onRefresh: () =>
context.read<ServicesCubit>().loadServices(refresh: true),
context.read<OperationsCubit>().loadOperations(refresh: true),
child: ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.only(bottom: 80), // Spazio per il FAB
itemCount: state.hasReachedMax
? state.allServices.length
: state.allServices.length + 1,
? state.allOperations.length
: state.allOperations.length + 1,
itemBuilder: (context, index) {
if (index >= state.allServices.length) {
if (index >= state.allOperations.length) {
return const Center(
child: Padding(
padding: EdgeInsets.all(16.0),
@@ -107,21 +107,21 @@ class _ServicesScreenState extends State<ServicesScreen> {
);
}
final operation = state.allServices[index];
return _buildServiceCard(context, operation);
final operation = state.allOperations[index];
return _buildOperationCard(context, operation);
},
),
);
},
),
floatingActionButton: FloatingActionButton(
onPressed: () => startNewService(context),
onPressed: () => startNewOperation(context),
child: const Icon(Icons.add),
),
);
}
Widget _buildServiceCard(BuildContext context, ServiceModel operation) {
Widget _buildOperationCard(BuildContext context, OperationModel operation) {
return Card(
margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
elevation: 2,
@@ -164,11 +164,11 @@ class _ServicesScreenState extends State<ServicesScreen> {
children: [
if (operation.al > 0 || operation.mnp > 0)
_miniBadge("📞 Tel", Colors.blue),
if (operation.energyServices.isNotEmpty)
if (operation.energyOperations.isNotEmpty)
_miniBadge("⚡ Energy", Colors.green),
if (operation.finServices.isNotEmpty)
if (operation.finOperations.isNotEmpty)
_miniBadge("💰 Fin", Colors.purple),
if (operation.entertainmentServices.isNotEmpty)
if (operation.entertainmentOperations.isNotEmpty)
_miniBadge("📺 Ent", Colors.red),
],
),
@@ -180,7 +180,7 @@ class _ServicesScreenState extends State<ServicesScreen> {
extra: operation, // <-- LA MAGIA È QUI: Passa l'oggetto intero!
// Teniamo anche il parametro URL per coerenza di routing
queryParameters: operation.id != null
? {'serviceId': operation.id!}
? {'operationId': operation.id!}
: {},
),
),