j
This commit is contained in:
@@ -1,14 +1,13 @@
|
||||
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/widgets/shared_forms/shared_files_section.dart';
|
||||
import 'package:flux/features/attachments/blocs/attachments_bloc.dart';
|
||||
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
||||
import 'package:flux/features/operations/blocs/operation_form_cubit.dart';
|
||||
import 'package:flux/features/operations/models/operation_model.dart';
|
||||
import 'package:flux/core/widgets/shared_forms/customer_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/staff_section.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
class OperationFormScreen extends StatefulWidget {
|
||||
final String? operationId;
|
||||
@@ -49,26 +48,10 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final cubit = context.read<OperationsCubit>();
|
||||
final currentLoggedStaff = GetIt.I
|
||||
.get<SessionCubit>()
|
||||
.state
|
||||
.currentStaffMember!;
|
||||
|
||||
// 1. Diciamo al Cubit di prepararsi
|
||||
cubit.initOperationForm(
|
||||
context.read<OperationFormCubit>().initForm(
|
||||
existingOperation: widget.existingOperation,
|
||||
operationId: widget.operationId,
|
||||
staffId: currentLoggedStaff.id,
|
||||
staffDisplayName: currentLoggedStaff.name,
|
||||
);
|
||||
|
||||
// 2. IL TRUCCO MAGICO:
|
||||
// Se abbiamo passato existingOperation, il Cubit si è appena aggiornato.
|
||||
// Lo stato è già pronto, quindi sincronizziamo i controller SUBITO!
|
||||
if (cubit.state.currentOperation != null) {
|
||||
_syncTextControllers(cubit.state.currentOperation!);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -76,50 +59,83 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
_referenceController.dispose();
|
||||
_noteController.dispose();
|
||||
_freeTextSubtypeController.dispose();
|
||||
_freeTextDescriptionController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _syncTextControllers(OperationModel model) {
|
||||
if (_referenceController.text.isEmpty && model.reference.isNotEmpty) {
|
||||
if (_referenceController.text.isEmpty) {
|
||||
_referenceController.text = model.reference;
|
||||
}
|
||||
if (_noteController.text.isEmpty && model.note.isNotEmpty) {
|
||||
if (_noteController.text.isEmpty) {
|
||||
_noteController.text = model.note;
|
||||
}
|
||||
if (_freeTextSubtypeController.text.isEmpty &&
|
||||
model.subtype != null &&
|
||||
model.subtype!.isNotEmpty) {
|
||||
_freeTextSubtypeController.text = model.subtype!;
|
||||
if (_freeTextSubtypeController.text.isEmpty) {
|
||||
_freeTextSubtypeController.text = model.subtype ?? '';
|
||||
}
|
||||
if (_freeTextDescriptionController.text.isEmpty &&
|
||||
model.description != null &&
|
||||
model.description!.isNotEmpty) {
|
||||
_freeTextDescriptionController.text = model.description!;
|
||||
if (_freeTextDescriptionController.text.isEmpty) {
|
||||
_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;
|
||||
}
|
||||
|
||||
void _saveOperation({required bool keepAdding}) {
|
||||
void _flushControllersToCubit() {
|
||||
context.read<OperationFormCubit>().updateFields(
|
||||
reference: _referenceController.text,
|
||||
note: _noteController.text,
|
||||
subtype: _freeTextSubtypeController.text,
|
||||
description: _freeTextDescriptionController.text,
|
||||
);
|
||||
}
|
||||
|
||||
void _saveOperation({
|
||||
required OperationStatus targetStatus,
|
||||
required bool keepAdding,
|
||||
}) {
|
||||
if (_formKey.currentState!.validate()) {
|
||||
final cubit = context.read<OperationsCubit>();
|
||||
final currentOperation = cubit.state.currentOperation!;
|
||||
|
||||
final operationToSave = currentOperation.copyWith(
|
||||
reference: _referenceController.text,
|
||||
note: _noteController.text,
|
||||
subtype: ['Entertainment', 'Custom'].contains(currentOperation.type)
|
||||
? _freeTextSubtypeController.text
|
||||
: currentOperation.subtype,
|
||||
description: ['Energy', 'Custom'].contains(currentOperation.type)
|
||||
? _freeTextDescriptionController.text
|
||||
: currentOperation.description,
|
||||
_flushControllersToCubit();
|
||||
context.read<OperationFormCubit>().saveOperation(
|
||||
targetStatus: targetStatus,
|
||||
keepAdding: keepAdding,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
cubit.initOperationForm(existingOperation: operationToSave);
|
||||
cubit.saveCurrentOperation(
|
||||
targetStatus: OperationStatus.ok,
|
||||
shouldPop: !keepAdding,
|
||||
);
|
||||
Future<String?> _generateIdForQr() async {
|
||||
if (!_formKey.currentState!.validate()) return null;
|
||||
_flushControllersToCubit();
|
||||
final attachmentsBloc = context.read<AttachmentsBloc>();
|
||||
// Presumo tu abbia creato il metodo saveOperationDraft() nel Cubit!
|
||||
final newId = await context.read<OperationFormCubit>().saveOperationDraft();
|
||||
if (newId != null && context.mounted) {
|
||||
attachmentsBloc.add(ParentEntitySavedEvent(newId));
|
||||
}
|
||||
return newId;
|
||||
}
|
||||
|
||||
// Helper per assegnare un colore agli stati
|
||||
Color _getStatusColor(OperationStatus status) {
|
||||
switch (status) {
|
||||
case OperationStatus.success:
|
||||
return Colors.green;
|
||||
case OperationStatus.waitingForAction:
|
||||
return Colors.orange;
|
||||
case OperationStatus.waitingForSupport:
|
||||
return Colors.blue;
|
||||
case OperationStatus.failure:
|
||||
return Colors.red;
|
||||
case OperationStatus.draft:
|
||||
return Colors.grey;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,33 +143,27 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return BlocConsumer<OperationsCubit, OperationsState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.status != current.status ||
|
||||
previous.currentOperation?.id != current.currentOperation?.id,
|
||||
return BlocConsumer<OperationFormCubit, OperationFormState>(
|
||||
listenWhen: (previous, current) => previous.status != current.status,
|
||||
listener: (context, state) {
|
||||
if (state.status == OperationsStatus.ready &&
|
||||
state.currentOperation != null &&
|
||||
!_isInitialized) {
|
||||
_syncTextControllers(state.currentOperation!);
|
||||
if (state.status == OperationFormStatus.ready && !_isInitialized) {
|
||||
_syncTextControllers(state.operation);
|
||||
}
|
||||
|
||||
if (state.status == OperationsStatus.saved) {
|
||||
if (state.status == OperationFormStatus.success) {
|
||||
Navigator.of(context).pop();
|
||||
} else if (state.status == OperationsStatus.savedNoPop) {
|
||||
context.read<OperationsCubit>().prepareNextOperationInBatch();
|
||||
} else if (state.status == OperationFormStatus.successAndAddAnother) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Servizio aggiunto! Inserisci il prossimo.'),
|
||||
content: Text('Operazione salvata! Inserisci la prossima'),
|
||||
),
|
||||
);
|
||||
_freeTextSubtypeController.clear();
|
||||
_freeTextDescriptionController.clear();
|
||||
} else if (state.status == OperationsStatus.failure) {
|
||||
} else if (state.status == OperationFormStatus.failure) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(state.errorMessage ?? 'Errore'),
|
||||
backgroundColor: theme.colorScheme.error,
|
||||
content: Text(state.errorMessage ?? 'Errore di salvataggio'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -161,19 +171,45 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
builder: (context, state) {
|
||||
if (!_isInitialized &&
|
||||
(widget.operationId != null || widget.existingOperation != null) &&
|
||||
state.status == OperationsStatus.loading) {
|
||||
state.status == OperationFormStatus.loading) {
|
||||
return const Scaffold(
|
||||
body: Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
// Determiniamo lo stato da mostrare nel form.
|
||||
// Se è una bozza appena creata, mostriamo visivamente "OK" come default per il salvataggio.
|
||||
final displayStatus =
|
||||
state.operation.status == OperationStatus.draft &&
|
||||
state.operation.id == null
|
||||
? OperationStatus.success
|
||||
: state.operation.status;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
state.currentOperation?.id == null
|
||||
? 'Nuova Pratica'
|
||||
: 'Modifica Pratica',
|
||||
state.operation.id == null ? 'Nuova Pratica' : 'Modifica Pratica',
|
||||
),
|
||||
// Mettiamo un piccolo indicatore visivo anche nella AppBar se non è OK
|
||||
actions:
|
||||
displayStatus != OperationStatus.success &&
|
||||
displayStatus != OperationStatus.draft
|
||||
? [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 16.0),
|
||||
child: Chip(
|
||||
label: Text(
|
||||
displayStatus.displayName,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
backgroundColor: _getStatusColor(displayStatus),
|
||||
),
|
||||
),
|
||||
]
|
||||
: null,
|
||||
),
|
||||
body: Form(
|
||||
key: _formKey,
|
||||
@@ -182,26 +218,22 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
final isUltraWide = constraints.maxWidth > 1400;
|
||||
final isDesktop = constraints.maxWidth > 900;
|
||||
if (isUltraWide) {
|
||||
// --- LAYOUT 3 COLONNE (Schermi giganti) ---
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
// 1. FORM PRINCIPALE (40%)
|
||||
Expanded(
|
||||
flex: 4,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
// Attenzione: devi togliere la sezione file dal _buildMainFormContent!
|
||||
child: _buildMainFormContent(
|
||||
theme,
|
||||
state,
|
||||
displayStatus,
|
||||
showFiles: false,
|
||||
),
|
||||
),
|
||||
),
|
||||
VerticalDivider(width: 1, color: theme.dividerColor),
|
||||
|
||||
// 2. NOTE (30%)
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: Padding(
|
||||
@@ -210,15 +242,15 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
),
|
||||
),
|
||||
VerticalDivider(width: 1, color: theme.dividerColor),
|
||||
|
||||
// 3. FILE (30%)
|
||||
Expanded(
|
||||
flex: 3,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: SharedAttachmentsSection(
|
||||
parentType: AttachmentParentType.operation,
|
||||
parentId: state.currentOperation?.id,
|
||||
child: SharedFilesSection(
|
||||
titleNameForUpload:
|
||||
state.operation.customerDisplayName ??
|
||||
'Nuova operazione',
|
||||
onGenerateIdForQr: _generateIdForQr,
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -232,7 +264,11 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
flex: 7,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: _buildMainFormContent(theme, state),
|
||||
child: _buildMainFormContent(
|
||||
theme,
|
||||
state,
|
||||
displayStatus,
|
||||
),
|
||||
),
|
||||
),
|
||||
VerticalDivider(width: 1, color: theme.dividerColor),
|
||||
@@ -251,7 +287,7 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildMainFormContent(theme, state),
|
||||
_buildMainFormContent(theme, state, displayStatus),
|
||||
const Divider(height: 32),
|
||||
_buildNotesSection(isDesktop: false),
|
||||
const SizedBox(height: 80),
|
||||
@@ -270,9 +306,13 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: OutlinedButton(
|
||||
onPressed: state.status == OperationsStatus.saving
|
||||
onPressed: state.status == OperationFormStatus.saving
|
||||
? null
|
||||
: () => _saveOperation(keepAdding: true),
|
||||
: () => _saveOperation(
|
||||
keepAdding: true,
|
||||
targetStatus:
|
||||
displayStatus, // <-- Usiamo lo stato selezionato nel form!
|
||||
),
|
||||
child: const Text(
|
||||
'Salva e Aggiungi Altro',
|
||||
textAlign: TextAlign.center,
|
||||
@@ -283,10 +323,27 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
Expanded(
|
||||
flex: 1,
|
||||
child: ElevatedButton(
|
||||
onPressed: state.status == OperationsStatus.saving
|
||||
style: ElevatedButton.styleFrom(
|
||||
// Se c'è un KO o un blocco, cambiamo il colore del bottone principale per attirare l'attenzione
|
||||
backgroundColor:
|
||||
displayStatus != OperationStatus.success &&
|
||||
displayStatus != OperationStatus.draft
|
||||
? _getStatusColor(displayStatus)
|
||||
: null,
|
||||
foregroundColor:
|
||||
displayStatus != OperationStatus.success &&
|
||||
displayStatus != OperationStatus.draft
|
||||
? Colors.white
|
||||
: null,
|
||||
),
|
||||
onPressed: state.status == OperationFormStatus.saving
|
||||
? null
|
||||
: () => _saveOperation(keepAdding: false),
|
||||
child: state.status == OperationsStatus.saving
|
||||
: () => _saveOperation(
|
||||
keepAdding: false,
|
||||
targetStatus:
|
||||
displayStatus, // <-- Usiamo lo stato selezionato nel form!
|
||||
),
|
||||
child: state.status == OperationFormStatus.saving
|
||||
? const SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
@@ -309,32 +366,102 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
|
||||
Widget _buildMainFormContent(
|
||||
ThemeData theme,
|
||||
OperationsState state, {
|
||||
OperationFormState state,
|
||||
OperationStatus displayStatus, {
|
||||
bool showFiles = true,
|
||||
}) {
|
||||
final currentOp = state.currentOperation;
|
||||
final currentType = currentOp?.type ?? 'AL';
|
||||
final currentOp = state.operation;
|
||||
final currentType = currentOp.type;
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
StaffSection(
|
||||
staffId: currentOp?.staffId,
|
||||
staffName: currentOp?.staffDisplayName,
|
||||
staffId: currentOp.staffId,
|
||||
staffName: currentOp.staffDisplayName,
|
||||
onStaffSelected: (staff) => {
|
||||
context.read<OperationsCubit>().updateOperationFields(
|
||||
context.read<OperationFormCubit>().updateFields(
|
||||
staffId: staff.id,
|
||||
staffDisplayName: staff.name,
|
||||
),
|
||||
},
|
||||
),
|
||||
const Divider(height: 50),
|
||||
|
||||
// --- SEZIONE STATO OPERAZIONE ---
|
||||
_buildSectionTitle('Esito / Stato Operazione'),
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
color: _getStatusColor(displayStatus).withValues(alpha: 0.1),
|
||||
border: Border.all(
|
||||
color: _getStatusColor(displayStatus).withValues(alpha: 0.3),
|
||||
),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: DropdownButton<OperationStatus>(
|
||||
isExpanded: true,
|
||||
value: displayStatus,
|
||||
icon: Icon(
|
||||
Icons.arrow_drop_down,
|
||||
color: _getStatusColor(displayStatus),
|
||||
),
|
||||
items: OperationStatus.values
|
||||
.where(
|
||||
(s) => s != OperationStatus.draft,
|
||||
) // Nascondiamo 'Bozza' dal menu
|
||||
.map(
|
||||
(status) => DropdownMenuItem(
|
||||
value: status,
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
status == OperationStatus.success
|
||||
? Icons.check_circle
|
||||
: Icons.error_outline,
|
||||
color: _getStatusColor(status),
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
Text(
|
||||
status.displayName,
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: _getStatusColor(status),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: (newStatus) {
|
||||
if (newStatus != null) {
|
||||
// Assicurati che il metodo updateFields nel tuo Cubit accetti anche 'status'
|
||||
context.read<OperationFormCubit>().updateFields(
|
||||
status: newStatus,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
displayStatus == OperationStatus.success
|
||||
? 'Lascia OK se la pratica è stata caricata con successo.'
|
||||
: 'Attenzione: la pratica verrà salvata come ${displayStatus.displayName}.',
|
||||
style: TextStyle(fontSize: 12, color: Colors.grey.shade600),
|
||||
),
|
||||
const Divider(height: 32),
|
||||
|
||||
_buildSectionTitle('Cliente & Riferimento'),
|
||||
SharedCustomerSection(
|
||||
customerId: currentOp?.customerId,
|
||||
customerName: currentOp?.customerDisplayName,
|
||||
customerId: currentOp.customerId,
|
||||
customerName: currentOp.customerDisplayName,
|
||||
onCustomerSelected: (customer) {
|
||||
context.read<OperationsCubit>().updateOperationFields(
|
||||
context.read<OperationFormCubit>().updateFields(
|
||||
customerId: customer.id,
|
||||
customerDisplayName: customer.name,
|
||||
);
|
||||
@@ -360,7 +487,9 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
selected: currentType == type,
|
||||
onSelected: (selected) {
|
||||
if (selected) {
|
||||
context.read<OperationsCubit>().setTypeWithSmartDefault(type);
|
||||
context.read<OperationFormCubit>().setTypeWithSmartDefault(
|
||||
type,
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -384,23 +513,23 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
IconButton(
|
||||
icon: const Icon(Icons.remove),
|
||||
onPressed: () {
|
||||
final q = currentOp?.quantity ?? 1;
|
||||
final q = currentOp.quantity;
|
||||
if (q > 1) {
|
||||
context.read<OperationsCubit>().updateOperationFields(
|
||||
context.read<OperationFormCubit>().updateFields(
|
||||
quantity: q - 1,
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
Text(
|
||||
'${currentOp?.quantity ?? 1}',
|
||||
'${currentOp.quantity}',
|
||||
style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
|
||||
),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.add),
|
||||
onPressed: () {
|
||||
final q = currentOp?.quantity ?? 1;
|
||||
context.read<OperationsCubit>().updateOperationFields(
|
||||
final q = currentOp.quantity;
|
||||
context.read<OperationFormCubit>().updateFields(
|
||||
quantity: q + 1,
|
||||
);
|
||||
},
|
||||
@@ -410,9 +539,14 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
const Divider(height: 32),
|
||||
|
||||
if (showFiles) ...[
|
||||
SharedAttachmentsSection(
|
||||
/* SharedAttachmentsSection(
|
||||
parentType: AttachmentParentType.operation,
|
||||
parentId: currentOp?.id,
|
||||
parentId: currentOp.id,
|
||||
), */
|
||||
SharedFilesSection(
|
||||
titleNameForUpload:
|
||||
state.operation.customerDisplayName ?? 'Nuova pratica',
|
||||
onGenerateIdForQr: _generateIdForQr,
|
||||
),
|
||||
],
|
||||
],
|
||||
@@ -444,7 +578,7 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||
backgroundColor: Colors.blue.withValues(alpha: 0.05),
|
||||
onPressed: () {
|
||||
final now = DateTime.now();
|
||||
context.read<OperationsCubit>().updateOperationFields(
|
||||
context.read<OperationFormCubit>().updateFields(
|
||||
expirationDate: DateTime(
|
||||
now.year,
|
||||
now.month + months,
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
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/blocs/operation_list_cubit.dart';
|
||||
import 'package:flux/features/operations/models/operation_model.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
// Importa i tuoi modelli e cubit
|
||||
|
||||
class OperationsScreen extends StatefulWidget {
|
||||
const OperationsScreen({super.key});
|
||||
class OperationListScreen extends StatefulWidget {
|
||||
const OperationListScreen({super.key});
|
||||
|
||||
@override
|
||||
State<OperationsScreen> createState() => _OperationsScreenState();
|
||||
State<OperationListScreen> createState() => _OperationListScreenState();
|
||||
}
|
||||
|
||||
class _OperationsScreenState extends State<OperationsScreen> {
|
||||
class _OperationListScreenState extends State<OperationListScreen> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
@@ -20,13 +20,11 @@ class _OperationsScreenState extends State<OperationsScreen> {
|
||||
super.initState();
|
||||
// Agganciamo il listener per la paginazione (Scroll Infinito)
|
||||
_scrollController.addListener(_onScroll);
|
||||
// Carichiamo i servizi iniziali
|
||||
context.read<OperationsCubit>().loadOperations();
|
||||
}
|
||||
|
||||
void _onScroll() {
|
||||
if (_isBottom) {
|
||||
context.read<OperationsCubit>().loadOperations();
|
||||
context.read<OperationListCubit>().loadOperations();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,16 +57,16 @@ class _OperationsScreenState extends State<OperationsScreen> {
|
||||
),
|
||||
],
|
||||
),
|
||||
body: BlocBuilder<OperationsCubit, OperationsState>(
|
||||
body: BlocBuilder<OperationListCubit, OperationListState>(
|
||||
builder: (context, state) {
|
||||
// 1. Stato di caricamento iniziale
|
||||
if (state.status == OperationsStatus.loading &&
|
||||
state.allOperations.isEmpty) {
|
||||
if (state.status == OperationListStatus.loading &&
|
||||
state.operations.isEmpty) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
|
||||
// 2. Lista vuota
|
||||
if (state.allOperations.isEmpty) {
|
||||
if (state.operations.isEmpty) {
|
||||
return Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -77,7 +75,7 @@ class _OperationsScreenState extends State<OperationsScreen> {
|
||||
const SizedBox(height: 10),
|
||||
ElevatedButton(
|
||||
onPressed: () => context
|
||||
.read<OperationsCubit>()
|
||||
.read<OperationListCubit>()
|
||||
.loadOperations(refresh: true),
|
||||
child: const Text("Riprova"),
|
||||
),
|
||||
@@ -88,16 +86,17 @@ class _OperationsScreenState extends State<OperationsScreen> {
|
||||
|
||||
// 3. La Lista (con Pull-to-refresh)
|
||||
return RefreshIndicator(
|
||||
onRefresh: () =>
|
||||
context.read<OperationsCubit>().loadOperations(refresh: true),
|
||||
onRefresh: () => context.read<OperationListCubit>().loadOperations(
|
||||
refresh: true,
|
||||
),
|
||||
child: ListView.builder(
|
||||
controller: _scrollController,
|
||||
padding: const EdgeInsets.only(bottom: 80), // Spazio per il FAB
|
||||
itemCount: state.hasReachedMax
|
||||
? state.allOperations.length
|
||||
: state.allOperations.length + 1,
|
||||
? state.operations.length
|
||||
: state.operations.length + 1,
|
||||
itemBuilder: (context, index) {
|
||||
if (index >= state.allOperations.length) {
|
||||
if (index >= state.operations.length) {
|
||||
return const Center(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.all(16.0),
|
||||
@@ -106,7 +105,7 @@ class _OperationsScreenState extends State<OperationsScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
final operation = state.allOperations[index];
|
||||
final operation = state.operations[index];
|
||||
return _buildOperationCard(context, operation);
|
||||
},
|
||||
),
|
||||
@@ -173,17 +172,16 @@ class _OperationsScreenState extends State<OperationsScreen> {
|
||||
Widget _buildOperationStatus(OperationStatus status) {
|
||||
Color color;
|
||||
switch (status) {
|
||||
case OperationStatus.canceled || OperationStatus.ko:
|
||||
case OperationStatus.failure:
|
||||
color = Colors.grey.shade800;
|
||||
break;
|
||||
case OperationStatus.waitingforaction || OperationStatus.draft:
|
||||
case OperationStatus.waitingForAction || OperationStatus.draft:
|
||||
color = Colors.orange;
|
||||
break;
|
||||
case OperationStatus.ok:
|
||||
case OperationStatus.success:
|
||||
color = Colors.green;
|
||||
break;
|
||||
case OperationStatus.waitingfordeployment ||
|
||||
OperationStatus.waitingforsupport:
|
||||
case OperationStatus.waitingForSupport:
|
||||
color = Colors.blue;
|
||||
break;
|
||||
}
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flux/core/widgets/shared_forms/model_section.dart';
|
||||
import 'package:flux/features/master_data/providers/blocs/provider_cubit.dart';
|
||||
import 'package:flux/features/operations/blocs/operations_cubit.dart';
|
||||
import 'package:flux/features/operations/blocs/operation_form_cubit.dart';
|
||||
import 'package:flux/features/operations/models/operation_model.dart';
|
||||
|
||||
class DetailsSection extends StatelessWidget {
|
||||
@@ -117,12 +117,10 @@ class DetailsSection extends StatelessWidget {
|
||||
),
|
||||
),
|
||||
onTap: () {
|
||||
context
|
||||
.read<OperationsCubit>()
|
||||
.updateOperationFields(
|
||||
providerId: provider.id,
|
||||
providerDisplayName: provider.name,
|
||||
);
|
||||
context.read<OperationFormCubit>().updateFields(
|
||||
providerId: provider.id,
|
||||
providerDisplayName: provider.name,
|
||||
);
|
||||
Navigator.pop(modalContext);
|
||||
},
|
||||
);
|
||||
@@ -190,9 +188,7 @@ class DetailsSection extends StatelessWidget {
|
||||
].map((s) => DropdownMenuItem(value: s, child: Text(s))).toList(),
|
||||
onChanged: (val) {
|
||||
if (val != null) {
|
||||
context.read<OperationsCubit>().updateOperationFields(
|
||||
subtype: val,
|
||||
);
|
||||
context.read<OperationFormCubit>().updateFields(subtype: val);
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -215,7 +211,7 @@ class DetailsSection extends StatelessWidget {
|
||||
modelId: currentOp?.modelId,
|
||||
modelName: currentOp?.modelDisplayName,
|
||||
onModelSelected: (id, name) {
|
||||
context.read<OperationsCubit>().updateOperationFields(
|
||||
context.read<OperationFormCubit>().updateFields(
|
||||
modelId: id,
|
||||
modelDisplayName: name,
|
||||
);
|
||||
@@ -271,7 +267,7 @@ class DetailsSection extends StatelessWidget {
|
||||
lastDate: DateTime.now().add(const Duration(days: 3650)),
|
||||
);
|
||||
if (date != null && context.mounted) {
|
||||
context.read<OperationsCubit>().updateOperationFields(
|
||||
context.read<OperationFormCubit>().updateFields(
|
||||
expirationDate: date,
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user