Compare commits
3 Commits
b60ce96dd7
...
v1.1.22
| Author | SHA1 | Date | |
|---|---|---|---|
| f8504d466a | |||
| 22bb86f052 | |||
| fc850795c9 |
@@ -34,14 +34,14 @@ class SharedAttachmentsSection extends StatefulWidget {
|
|||||||
final String? parentId;
|
final String? parentId;
|
||||||
final String titleForUpload;
|
final String titleForUpload;
|
||||||
final AttachmentParentType parentType;
|
final AttachmentParentType parentType;
|
||||||
final Future<String?> Function()? onGenerateIdForQr;
|
final Future<String?> Function()? onEnsureEntitySaved;
|
||||||
|
|
||||||
const SharedAttachmentsSection({
|
const SharedAttachmentsSection({
|
||||||
super.key,
|
super.key,
|
||||||
this.parentId,
|
this.parentId,
|
||||||
this.titleForUpload = 'Cliente_sconosciuto',
|
this.titleForUpload = 'Cliente_sconosciuto',
|
||||||
required this.parentType,
|
required this.parentType,
|
||||||
this.onGenerateIdForQr,
|
this.onEnsureEntitySaved,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -90,6 +90,32 @@ class _SharedAttachmentsSectionState extends State<SharedAttachmentsSection> {
|
|||||||
|
|
||||||
// --- SELEZIONE FILE DAL PC/TELEFONO ---
|
// --- SELEZIONE FILE DAL PC/TELEFONO ---
|
||||||
Future<void> _pickFiles() async {
|
Future<void> _pickFiles() async {
|
||||||
|
final attachmentsBloc = context.read<AttachmentsBloc>();
|
||||||
|
String? targetId = attachmentsBloc.state.parentId;
|
||||||
|
|
||||||
|
// 🥷 SE L'ID NON C'È (Nuova Operazione), FORZIAMO IL SALVATAGGIO PREVENTIVO!
|
||||||
|
if (targetId == null || targetId.isEmpty) {
|
||||||
|
if (widget.onEnsureEntitySaved != null) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text('Salvataggio rapido scheda per allegati... ⏳'),
|
||||||
|
duration: Duration(seconds: 1),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Chiamiamo la funzione passata dal TicketForm/OperationForm
|
||||||
|
targetId = await widget.onEnsureEntitySaved!();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se il salvataggio fallisce (es. form non valido), ci fermiamo per evitare file orfani
|
||||||
|
if (targetId == null || targetId.isEmpty) return;
|
||||||
|
|
||||||
|
// Comunichiamo immediatamente al BLoC che l'entità padre è stata salvata e ha un nuovo ID.
|
||||||
|
// Questo eviterà che i file finiscano nei `localFiles` temporanei.
|
||||||
|
attachmentsBloc.add(ParentEntitySavedEvent(targetId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ora che abbiamo la certezza matematica di avere un targetId, apriamo il picker
|
||||||
final result = await FilePicker.pickFiles(
|
final result = await FilePicker.pickFiles(
|
||||||
allowMultiple: true,
|
allowMultiple: true,
|
||||||
type: FileType.custom,
|
type: FileType.custom,
|
||||||
@@ -98,8 +124,8 @@ class _SharedAttachmentsSectionState extends State<SharedAttachmentsSection> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (result != null && mounted) {
|
if (result != null && mounted) {
|
||||||
// MAGIA: Passiamo direttamente la lista di PlatformFile al tuo BLoC!
|
// Ora il BLoC eseguirà l'ambiente di "Upload immediato" (Bivio 2) perché ha l'ID aggiornato!
|
||||||
context.read<AttachmentsBloc>().add(AddAttachmentsEvent(result.files));
|
attachmentsBloc.add(AddAttachmentsEvent(result.files));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -481,7 +507,7 @@ class _SharedAttachmentsSectionState extends State<SharedAttachmentsSection> {
|
|||||||
|
|
||||||
// SE L'ID NON C'È, CHIAMIAMO IL SALVATAGGIO IN BACKGROUND!
|
// SE L'ID NON C'È, CHIAMIAMO IL SALVATAGGIO IN BACKGROUND!
|
||||||
if (targetId == null) {
|
if (targetId == null) {
|
||||||
if (widget.onGenerateIdForQr != null) {
|
if (widget.onEnsureEntitySaved != null) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
@@ -492,7 +518,7 @@ class _SharedAttachmentsSectionState extends State<SharedAttachmentsSection> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Aspettiamo che il TicketFormCubit faccia il suo lavoro
|
// Aspettiamo che il TicketFormCubit faccia il suo lavoro
|
||||||
targetId = await widget.onGenerateIdForQr!();
|
targetId = await widget.onEnsureEntitySaved!();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se fallisce (es. validazione form non passata), ci fermiamo
|
// Se fallisce (es. validazione form non passata), ci fermiamo
|
||||||
|
|||||||
@@ -111,69 +111,6 @@ class OperationsRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- RECUPERO PAGINATO CON FILTRI E JOIN ---
|
|
||||||
/* Future<List<OperationModel>> fetchOperations({
|
|
||||||
required String companyId,
|
|
||||||
String? storeId,
|
|
||||||
String? staffId,
|
|
||||||
String? providerId,
|
|
||||||
required int offset,
|
|
||||||
int limit = 50,
|
|
||||||
String? searchTerm,
|
|
||||||
DateTimeRange? dateRange,
|
|
||||||
}) async {
|
|
||||||
try {
|
|
||||||
var query = _supabase
|
|
||||||
.from(Tables.operations)
|
|
||||||
.select('''
|
|
||||||
*,
|
|
||||||
${Tables.customers}(*),
|
|
||||||
${Tables.stores}(name),
|
|
||||||
${Tables.providers}(name),
|
|
||||||
${Tables.models}(name_with_brand),
|
|
||||||
${Tables.staffMembers}(name),
|
|
||||||
${Tables.attachments}(*)
|
|
||||||
''')
|
|
||||||
.eq('company_id', companyId);
|
|
||||||
|
|
||||||
// Filtro Range Date
|
|
||||||
if (dateRange != null) {
|
|
||||||
query = query
|
|
||||||
.gte('created_at', dateRange.start.toIso8601String())
|
|
||||||
.lte('created_at', dateRange.end.toIso8601String());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (storeId != null) {
|
|
||||||
query = query.or('store_id.eq.$storeId,store_id.is.null');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (staffId != null) {
|
|
||||||
query = query.or('staff_id.eq.$staffId,staff_id.is.null');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (providerId != null) {
|
|
||||||
query = query.or('provider_id.eq.$providerId,provider_id.is.null');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (searchTerm != null && searchTerm.isNotEmpty) {
|
|
||||||
// Filtra sui campi della tabella principale O su quelli della tabella joinata
|
|
||||||
query = query.or(
|
|
||||||
'reference.ilike.%$searchTerm%,note.ilike.%$searchTerm%,customer.name.ilike.%$searchTerm%',
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
final response = await query
|
|
||||||
.order('created_at', ascending: false)
|
|
||||||
.range(offset, offset + limit - 1);
|
|
||||||
|
|
||||||
return (response as List)
|
|
||||||
.map((map) => OperationModel.fromMap(map))
|
|
||||||
.toList();
|
|
||||||
} catch (e) {
|
|
||||||
throw Exception('$e');
|
|
||||||
}
|
|
||||||
} */
|
|
||||||
|
|
||||||
Stream<List<Map<String, dynamic>>> watchStoreOperations({
|
Stream<List<Map<String, dynamic>>> watchStoreOperations({
|
||||||
required String storeId,
|
required String storeId,
|
||||||
required int limit,
|
required int limit,
|
||||||
|
|||||||
@@ -26,6 +26,9 @@ class OperationFormScreen extends StatefulWidget {
|
|||||||
class _OperationFormScreenState extends State<OperationFormScreen> {
|
class _OperationFormScreenState extends State<OperationFormScreen> {
|
||||||
final _formKey = GlobalKey<FormState>();
|
final _formKey = GlobalKey<FormState>();
|
||||||
|
|
||||||
|
// 🥷 IL NUOVO FLAG: Ci ricorderà se vogliamo davvero uscire!
|
||||||
|
bool _isClosingIntent = false;
|
||||||
|
|
||||||
final _referenceController = TextEditingController();
|
final _referenceController = TextEditingController();
|
||||||
final _noteController = TextEditingController();
|
final _noteController = TextEditingController();
|
||||||
final _freeTextSubtypeController = TextEditingController();
|
final _freeTextSubtypeController = TextEditingController();
|
||||||
@@ -101,10 +104,11 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
required bool keepAdding,
|
required bool keepAdding,
|
||||||
}) {
|
}) {
|
||||||
if (_formKey.currentState!.validate()) {
|
if (_formKey.currentState!.validate()) {
|
||||||
|
// Se non stiamo facendo "Salva e Aggiungi Altro", il nostro intento è chiudere
|
||||||
|
_isClosingIntent = !keepAdding;
|
||||||
|
|
||||||
_flushControllersToCubit();
|
_flushControllersToCubit();
|
||||||
// Aggiorniamo prima lo stato bersaglio nel cubit
|
|
||||||
context.read<OperationFormCubit>().updateFields(status: targetStatus);
|
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,
|
||||||
@@ -112,11 +116,15 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String?> _generateIdForQr() async {
|
// RINOMINATA E RESA SICURA
|
||||||
|
Future<String?> _ensureEntitySaved() async {
|
||||||
if (!_formKey.currentState!.validate()) return null;
|
if (!_formKey.currentState!.validate()) return null;
|
||||||
|
|
||||||
|
// 🥷 Diciamo esplicitamente al sistema che NON vogliamo uscire dalla pagina
|
||||||
|
_isClosingIntent = false;
|
||||||
|
|
||||||
_flushControllersToCubit();
|
_flushControllersToCubit();
|
||||||
|
|
||||||
// Lo leggiamo pulito pulito dal context, perché c'è!
|
|
||||||
final attachmentsBloc = context.read<AttachmentsBloc>();
|
final attachmentsBloc = context.read<AttachmentsBloc>();
|
||||||
|
|
||||||
final newId = await context.read<OperationFormCubit>().saveOperationDraft();
|
final newId = await context.read<OperationFormCubit>().saveOperationDraft();
|
||||||
@@ -151,8 +159,15 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
if (state.status == OperationFormStatus.ready && !_isInitialized) {
|
if (state.status == OperationFormStatus.ready && !_isInitialized) {
|
||||||
_syncTextControllers(state.operation);
|
_syncTextControllers(state.operation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 🥷 ORA POPPA SOLO SE L'INTENTO ERA QUELLO DI USCIRE!
|
||||||
if (state.status == OperationFormStatus.success) {
|
if (state.status == OperationFormStatus.success) {
|
||||||
Navigator.of(context).pop();
|
if (_isClosingIntent) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
} else {
|
||||||
|
// È stato un salvataggio background (Pick Files o QR).
|
||||||
|
// Non facciamo nulla, l'utente resta sulla pagina e i file vengono caricati!
|
||||||
|
}
|
||||||
} else if (state.status == OperationFormStatus.successAndAddAnother) {
|
} else if (state.status == OperationFormStatus.successAndAddAnother) {
|
||||||
ScaffoldMessenger.of(context).showSnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
const SnackBar(
|
const SnackBar(
|
||||||
@@ -626,7 +641,7 @@ class _OperationFormScreenState extends State<OperationFormScreen> {
|
|||||||
parentType: AttachmentParentType.operation,
|
parentType: AttachmentParentType.operation,
|
||||||
parentId: state.operation.id,
|
parentId: state.operation.id,
|
||||||
titleForUpload: state.operation.customer?.name ?? 'Nuova Pratica',
|
titleForUpload: state.operation.customer?.name ?? 'Nuova Pratica',
|
||||||
onGenerateIdForQr: _generateIdForQr,
|
onEnsureEntitySaved: _ensureEntitySaved, // 🥷 Il nuovo nome!
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
name: flux
|
name: flux
|
||||||
description: "Gestione attività negozio di telefonia"
|
description: "Gestione attività negozio di telefonia"
|
||||||
publish_to: 'none'
|
publish_to: 'none'
|
||||||
version: 1.1.21+39
|
version: 1.1.22+40
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ^3.11.3
|
sdk: ^3.11.3
|
||||||
|
|||||||
Reference in New Issue
Block a user